Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewbub committed Oct 15, 2023
0 parents commit 9c01e89
Show file tree
Hide file tree
Showing 16 changed files with 2,650 additions and 0 deletions.
35 changes: 35 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# PoS-Viz (Parts of Speech Visualizer)

The goal is to create a text editor that visually represents words' grammatical roles (parts of speech) using color coding.

The user enters text into a text area, and each word in the text area is wrapped in a span tag; then, the part of speech for each word is identified using NLP, and the span tag is given a class name based on the part of speech. The class name is then used to color the word.

This project uses JavaScript and is a practical introduction to NLP with [Compromise](https://compromise.cool/) and cutting-edge concepts introduced in early Next.js v13 and React.js v18 versions.

## Set up

This project was bootstrapped using the `npx create-next-app@latest` CLI command. To pull this up on a local machine, clone the repo and run `npm install` to install the project dependencies. Then, run `npm run dev` to start the development server.
23 changes: 23 additions & 0 deletions app/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use server'
import npl from 'compromise';

export async function handleFormSubmission(prevState, formData) {
try {
const textareaValue = formData.get('textarea');

const doc = npl(textareaValue);
const tagged = doc.sentences().terms().out('tags')

const parsedMarkup = tagged.reduce((acc, curr) => {
const word = Object.keys(curr)[0]
const pos = Object.values(curr)[0][0]

return acc + `<span class="${pos.toLowerCase()}" title="${pos}">${word}</span> `;
}, '');

return { textarea: parsedMarkup, success: true }
} catch (e) {
console.error(e)
return { textarea: null, success: false }
}
}
Binary file added app/favicon.ico
Binary file not shown.
156 changes: 156 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

.home {
@apply lg:grid lg:grid-cols-2 pt-12;
}

.home__form {
@apply mx-auto lg:mr-0 lg:ml-auto max-w-sm lg:max-w-[500px] lg:w-[500px] flex flex-col lg:px-4;
}

.home__label {
@apply font-bold;
}

.home__textarea {
@apply h-24 w-full;
}

.home__button-container {
@apply w-full mt-2;
}

.home__button {
@apply border border-gray-500 px-3 py-1;
}

.home__aside {
@apply max-w-sm mx-auto lg:mx-0 lg:px-4;
}

.home__title {
@apply font-bold leading-7;
}

/* PoS Classes */
span {
@apply inline-block px-1 rounded-sm leading-5;
}

.preposition {
@apply bg-red-500/50;
}

.date {
@apply bg-blue-500/50;
}

.noun {
@apply bg-green-500/50;
}

.expression {
@apply bg-yellow-500/50;
}

.possessive {
@apply bg-yellow-500/50;
}

.adverb {
@apply bg-indigo-500/50;
}

.verb {
@apply bg-purple-500/50;
}

.singular {
@apply bg-pink-500/50;
}

.adjective {
@apply bg-gray-500/50;
}

.plural {
@apply bg-orange-500/50;
}

.actor {
@apply bg-teal-500/50;
}

.presenttense {
@apply bg-red-600/50;
}

.infinitive {
@apply bg-blue-600/50;
}

.uncountable {
@apply bg-green-600/50;
}

.determiner {
@apply bg-yellow-600/50;
}

.conjunction {
@apply bg-indigo-600/50;
}

.value {
@apply bg-purple-600/50;
}

.textvalue {
@apply bg-pink-600/50;
}

.cardinal {
@apply bg-gray-600/50;
}

.propernoun {
@apply bg-orange-600/50;
}

.hyphenated {
@apply bg-teal-600/50;
}

.copula {
@apply bg-red-700/50;
}

.gerund {
@apply bg-blue-700/50;
}

.pronoun {
@apply bg-green-700/50;
}

.pasttense {
@apply bg-yellow-700/50;
}

.acronym {
@apply bg-indigo-700/50;
}

.imperative {
@apply bg-purple-700/50;
}

.questionword {
@apply bg-pink-700/50;
}

.abbreviation {
@apply bg-gray-700/50;
}
17 changes: 17 additions & 0 deletions app/layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import './globals.css'
import { Inter } from 'next/font/google'

const inter = Inter({ subsets: ['latin'] })

export const metadata = {
title: 'PoS Viz',
description: 'A text editor that visually represents the grammatical roles (parts of speech) of words using color-coding.',
}

export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
)
}
33 changes: 33 additions & 0 deletions app/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use client';
import { experimental_useFormState as useFormState } from 'react-dom'
import { handleFormSubmission } from './actions';

export default function Home() {
const [state, formAction] = useFormState(handleFormSubmission, {
textarea: '',
success: false,
})

return (
<main className="home">
<form action={formAction} className='home__form'>
<label htmlFor='textarea' className='home__label'>Parts of Speech (PoS) Visualizer</label>
<textarea id='textarea' name='textarea' className="home__textarea" defaultValue={'In the interconnected world of modern tech, APIs play a pivotal role. Crafted by adept developers, these interfaces connect diverse systems seamlessly. They bridge gaps, facilitate data exchange, and enhance interoperability, making integrations smooth and efficient.'}/>
<div className="home__button-container">
<button type="submit" className="home__button">
PoSify
</button>
</div>
</form>

<aside className='home__aside'>
<h2 className="home__title">Output</h2>
<div>
{state?.textarea && state?.textarea.length > 0 && (
<div dangerouslySetInnerHTML={{__html: state?.textarea }} />
)}
</div>
</aside>
</main>
)
}
7 changes: 7 additions & 0 deletions jsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./*"]
}
}
}
8 changes: 8 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
serverActions: true,
}
}

module.exports = nextConfig
Loading

0 comments on commit 9c01e89

Please sign in to comment.