Spaces:
Running
Running
| <script lang="ts"> | |
| import { onMount } from "svelte"; | |
| import SvelteTable, { type TableColumn } from "svelte-table"; | |
| import Row from "./Row"; | |
| import EditButtonComponent from "./EditButtonComponent.svelte"; | |
| import EditModal from "./EditModal.svelte"; | |
| import { fetchRows, addEntry } from "./bridge"; | |
| import { statusMessage } from "./store"; | |
| import SearchButtonComponent from "./SearchButtonComponent.svelte"; | |
| let rows: Row[] = []; | |
| let columns: TableColumn<Row>[] = [ | |
| { | |
| key: "Name", | |
| title: "Name", | |
| value: (v: Row) => v.Name || v.Project || v.Code || v.Paper || "N/A", | |
| sortable: true, | |
| renderValue: (v: Row) => { | |
| let url; | |
| if (v.Project) { | |
| url = v.Project; | |
| } else if (v.Code) { | |
| url = v.Code; | |
| } else if (v.Paper) { | |
| url = v.Paper; | |
| } else if (v.Space) { | |
| url = v.Space; | |
| } else if (v.Model) { | |
| url = v.Model; | |
| } else if (v.Dataset) { | |
| url = v.Dataset; | |
| } | |
| let name = v.Name || url || ""; | |
| let renderName = name; | |
| if (name.length > 64) { | |
| renderName = name.slice(0, 60) + "..."; | |
| } | |
| return `<a href="${url}" target="_blank" title="${name}">${renderName}</a>`; | |
| }, | |
| searchValue: (v: Row) => { | |
| let searchValue = v.Name || ""; | |
| if (v.Project) { | |
| searchValue += " " + v.Project; | |
| } | |
| if (v.Code) { | |
| searchValue += " " + v.Code; | |
| } | |
| if (v.Paper) { | |
| searchValue += " " + v.Paper; | |
| } | |
| if (v.Space) { | |
| searchValue += " " + v.Space; | |
| } | |
| if (v.Model) { | |
| searchValue += " " + v.Model; | |
| } | |
| if (v.Dataset) { | |
| searchValue += " " + v.Dataset; | |
| } | |
| return searchValue; | |
| }, | |
| parseHTML: true, | |
| hideFilterHeader: true, | |
| }, | |
| { | |
| key: "Edit", | |
| title: "", | |
| value: (v: Row) => "", | |
| sortable: false, | |
| renderComponent: EditButtonComponent, | |
| hideFilterHeader: true, | |
| }, | |
| { | |
| key: "Search", | |
| title: "", | |
| value: (v: Row) => "", | |
| sortable: false, | |
| renderComponent: SearchButtonComponent, | |
| hideFilterHeader: true, | |
| }, | |
| { | |
| key: "ReachedOut", | |
| title: "Reached Out", | |
| value: (v: Row) => { | |
| if (v.Tags.includes("ReachedOut")) { | |
| return "\u2705"; | |
| } else if (v.Tags.includes("NoReachedOut")) { | |
| return "\u274C"; | |
| } else { | |
| return ""; | |
| } | |
| }, | |
| sortable: true, | |
| filterValue: (v: Row) => { | |
| if (v.Tags.includes("ReachedOut")) { | |
| return "Yes"; | |
| } else if (v.Tags.includes("NoReachedOut")) { | |
| return "N/A"; | |
| } | |
| return "No"; | |
| }, | |
| filterOptions: ["Yes", "No", "N/A"], | |
| }, | |
| { | |
| key: "Date", | |
| title: "Date", | |
| value: (v: Row) => v.Date || "", | |
| sortable: true, | |
| searchValue: (v: Row) => v.Date || "", | |
| renderValue: (v: Row) => { | |
| if (!v.Date) return ""; | |
| let renderDate = v.Date; | |
| // Convert ISO to YYYY-MM-DD | |
| if (v.Date.includes("T")) { | |
| renderDate = v.Date.split("T")[0]; | |
| } | |
| return `<span title="${v.Date}">${renderDate}</span>`; | |
| }, | |
| parseHTML: true, | |
| }, | |
| { | |
| key: "Orgs", | |
| title: "Orgs", | |
| value: (v: Row) => v.Orgs.join(", "), | |
| sortable: true, | |
| searchValue: (v: Row) => v.Orgs.join(" "), | |
| renderValue: (v: Row) => { | |
| let orgs = v.Orgs.join(", "); | |
| let renderOrgs = v.Orgs.join(", "); | |
| if (orgs.length > 24) { | |
| renderOrgs = renderOrgs.slice(0, 20) + "..."; | |
| } | |
| return `<span title="${orgs}">${renderOrgs}</span>`; | |
| }, | |
| parseHTML: true, | |
| }, | |
| { | |
| key: "Authors", | |
| title: "Authors", | |
| value: (v: Row) => v.Authors.join(", "), | |
| sortable: true, | |
| searchValue: (v: Row) => v.Authors.join(" "), | |
| renderValue: (v: Row) => { | |
| let authors = v.Authors.join(", "); | |
| let renderAuthors = v.Authors.join(", "); | |
| if (authors.length > 24) { | |
| renderAuthors = renderAuthors.slice(0, 20) + "..."; | |
| } | |
| return `<span title="${authors}">${renderAuthors}</span>`; | |
| }, | |
| parseHTML: true, | |
| }, | |
| { | |
| key: "Paper", | |
| title: "Paper", | |
| value: (v: Row) => v.Paper || "", | |
| sortable: true, | |
| searchValue: (v: Row) => v.Paper || "", | |
| renderValue: (v: Row) => { | |
| if (!v.Paper) return ""; | |
| const url = v.Paper; | |
| let name = v.Paper; | |
| if (url.includes("arxiv.org/")) { | |
| name = `arxiv/${url.split("arxiv.org/")[1]}`; | |
| } else if (url.includes("huggingface.co/papers/")) { | |
| name = `hf/${url.split("huggingface.co/papers/")[1]}`; | |
| } | |
| let displayName = name.replace(".pdf", ""); | |
| if (displayName.length > 24) { | |
| displayName = displayName.slice(0, 20) + "..."; | |
| } | |
| return `<a href="${url}" target="_blank" title="${name}">${displayName}</a>`; | |
| }, | |
| parseHTML: true, | |
| }, | |
| { | |
| key: "Code", | |
| title: "Code", | |
| value: (v: Row) => v.CodeStatus || v.Code || "", | |
| sortable: true, | |
| searchValue: (v: Row) => { | |
| let searchValue = v.Code || ""; | |
| if (v.CodeStatus === "Coming Soon") { | |
| searchValue += " Coming Soon"; | |
| } else if (v.CodeStatus === "No") { | |
| searchValue += " No"; | |
| } | |
| return searchValue; | |
| }, | |
| renderValue: (v: Row) => { | |
| let codeUrl; | |
| let codeDisplay; | |
| if (v.Code) { | |
| codeUrl = v.Code; | |
| if (v.Code.includes("github.com")) { | |
| codeDisplay = `gh/${v.Code.split("github.com/")[1].slice(0, 16)}`; | |
| } else { | |
| codeDisplay = v.Code.slice(0, 16) + "..."; | |
| } | |
| } | |
| if (v.CodeStatus === "Coming Soon") { | |
| codeDisplay = "Coming Soon"; | |
| } else if (v.CodeStatus === "No") { | |
| codeDisplay = "No"; | |
| } | |
| if (!codeDisplay) { | |
| return ""; | |
| } | |
| if (codeUrl) { | |
| return `<a href="${codeUrl}" target="_blank" title="${codeUrl}">${codeDisplay}</a>`; | |
| } | |
| return codeDisplay; | |
| }, | |
| parseHTML: true, | |
| }, | |
| { | |
| key: "License", | |
| title: "License", | |
| value: (v: Row) => v.License || "", | |
| sortable: true, | |
| searchValue: (v: Row) => v.License || "", | |
| renderValue: (v: Row) => { | |
| if (!v.License) return ""; | |
| let displayLicense = v.License; | |
| if (v.License.length > 24) { | |
| displayLicense = v.License.slice(0, 20) + "..."; | |
| } | |
| return `<span title="${v.License}">${displayLicense}</span>`; | |
| }, | |
| parseHTML: true, | |
| }, | |
| { | |
| key: "Space", | |
| title: "Space", | |
| value: (v: Row) => v.Space || "", | |
| sortable: true, | |
| searchValue: (v: Row) => v.Space || "", | |
| renderValue: (v: Row) => { | |
| if (!v.Space) return ""; | |
| if (v.Space.includes("huggingface.co")) { | |
| return `<a href="${v.Space}" target="_blank">hf/${v.Space.split("huggingface.co/")[1].slice( | |
| 0, | |
| 16 | |
| )}</a>`; | |
| } | |
| return `<a href="${v.Space}" target="_blank" title="${v.Space}">${v.Space.slice(0, 16)}...</a>`; | |
| }, | |
| parseHTML: true, | |
| }, | |
| { | |
| key: "Model", | |
| title: "Model", | |
| value: (v: Row) => v.Model || "", | |
| sortable: true, | |
| searchValue: (v: Row) => v.Model || "", | |
| renderValue: (v: Row) => { | |
| if (!v.Model) return ""; | |
| if (v.Model.includes("huggingface.co")) { | |
| return `<a href="${v.Model}" target="_blank">hf/${v.Model.split("huggingface.co/")[1].slice( | |
| 0, | |
| 16 | |
| )}</a>`; | |
| } | |
| return `<a href="${v.Model}" target="_blank" title="${v.Model}">${v.Model.slice(0, 16)}...</a>`; | |
| }, | |
| parseHTML: true, | |
| }, | |
| { | |
| key: "Dataset", | |
| title: "Dataset", | |
| value: (v: Row) => v.Dataset || "", | |
| sortable: true, | |
| searchValue: (v: Row) => v.Dataset || "", | |
| renderValue: (v: Row) => { | |
| if (!v.Dataset) return ""; | |
| if (v.Dataset.includes("huggingface.co")) { | |
| return `<a href="${v.Dataset}" target="_blank">hf/${v.Dataset.split("huggingface.co/")[1].slice( | |
| 0, | |
| 16 | |
| )}</a>`; | |
| } else if (v.Dataset.includes("drive.google.com")) { | |
| return `<a href="${v.Dataset}" target="_blank">drive/${v.Dataset.split( | |
| "drive.google.com/" | |
| )[1].slice(0, 16)}</a>`; | |
| } else if (v.Dataset.includes("github.com")) { | |
| return `<a href="${v.Dataset}" target="_blank">gh/${v.Dataset.split("github.com/")[1].slice( | |
| 0, | |
| 16 | |
| )}</a>`; | |
| } | |
| return `<a href="${v.Dataset}" target="_blank" title="${v.Dataset}">${v.Dataset.slice(0, 16)}...</a>`; | |
| }, | |
| parseHTML: true, | |
| }, | |
| { | |
| key: "Added", | |
| title: "Added", | |
| value: (v: Row) => v.Added || "", | |
| sortable: true, | |
| searchValue: (v: Row) => v.Added || "", | |
| renderValue: (v: Row) => { | |
| if (!v.Added) return ""; | |
| let renderDate = v.Added; | |
| // Convert ISO to YYYY-MM-DD | |
| if (v.Added.includes("T")) { | |
| renderDate = v.Added.split("T")[0]; | |
| } | |
| return `<span title="${v.Added}">${renderDate}</span>`; | |
| }, | |
| parseHTML: true, | |
| }, | |
| ]; | |
| let selection: Record<string | number, any> = {}; | |
| let overrideSelection = false; | |
| let searchValue = ""; | |
| $: { | |
| if (searchValue !== "") { | |
| selection = { Name: searchValue }; | |
| overrideSelection = true; | |
| } else { | |
| if (overrideSelection) { | |
| selection = {}; | |
| overrideSelection = false; | |
| } | |
| } | |
| } | |
| onMount(async () => { | |
| rows = (await fetchRows()) || []; | |
| }); | |
| async function newEntry() { | |
| const row = await addEntry(selection); | |
| if (!row) return; | |
| rows = [row, ...rows]; | |
| } | |
| function handleRowUpdated(event: Event) { | |
| const updatedRow = (event as CustomEvent).detail; | |
| const rowIndex = rows.findIndex( | |
| (row) => | |
| row.Code === updatedRow.Code || row.Paper === updatedRow.Paper || row.Project === updatedRow.Project | |
| ); | |
| if (rowIndex !== -1) { | |
| rows[rowIndex] = updatedRow; | |
| } else { | |
| console.error("Row not found"); | |
| } | |
| } | |
| function handleRowDeleted(event: Event) { | |
| const deletedRow = (event as CustomEvent).detail; | |
| const rowIndex = rows.findIndex( | |
| (row) => | |
| row.Code === deletedRow.Code || row.Paper === deletedRow.Paper || row.Project === deletedRow.Project | |
| ); | |
| if (rowIndex !== -1) { | |
| rows = [...rows.slice(0, rowIndex), ...rows.slice(rowIndex + 1)]; | |
| } else { | |
| console.error("Row not found"); | |
| } | |
| } | |
| let fetching = false; | |
| let isModalOpen = false; | |
| </script> | |
| {#if !import.meta.env.VITE_HF_TOKEN} | |
| <div style="text-align: center; margin-top: 1rem;"> | |
| <p>Missing VITE_HF_TOKEN environment variable</p> | |
| </div> | |
| {:else if fetching} | |
| <div style="text-align: center; margin-top: 1rem;"> | |
| <p>{@html $statusMessage}</p> | |
| </div> | |
| {:else} | |
| <div style="text-align: center; margin-top: 1rem;"> | |
| <input | |
| type="text" | |
| placeholder="Enter relevant project, code, paper, space, model, or dataset URL" | |
| style="width: 100%; max-width: 512px;" | |
| bind:value={searchValue} | |
| /> | |
| <button on:click={newEntry}>New Entry</button> | |
| </div> | |
| {#if statusMessage} | |
| <div style="text-align: center; margin-top: 0.5rem;"> | |
| {#if !isModalOpen} | |
| {@html $statusMessage} | |
| {/if} | |
| </div> | |
| {/if} | |
| <!--spacer--> | |
| <div style="height: 1rem;" /> | |
| <SvelteTable {columns} {rows} sortBy="Date" sortOrder={-1} bind:filterSelections={selection} /> | |
| {/if} | |
| <EditModal bind:isOpen={isModalOpen} on:rowUpdated={handleRowUpdated} on:rowDeleted={handleRowDeleted} /> | |
| <style> | |
| :global(td) { | |
| border-top: 1px solid #e0e0e0; | |
| } | |
| </style> | |