Skip to content

Commit

Permalink
Merge pull request #9 from jhancock532/add-group-script-and-drawing-club
Browse files Browse the repository at this point in the history
Add new group script and drawing group
  • Loading branch information
jhancock532 authored Oct 4, 2024
2 parents a04d668 + 1ce285e commit 3da580b
Show file tree
Hide file tree
Showing 11 changed files with 194 additions and 69 deletions.
6 changes: 5 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@
PLAYWRIGHT_BASE_URL=http://host.docker.internal:6006

# Anthropic API key
ANTHROPIC_API_KEY=
ANTHROPIC_API_KEY=

# Anthropic AI model - can be any of the following values:
# claude-3-5-sonnet-20240620, claude-3-haiku-20240307, claude-3-opus-20240229
ANTHROPIC_MODEL=claude-3-5-sonnet-20240620
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# Bristol Social Groups

A website listing social groups in Bristol, UK. [View the live site](https://bristolsocialgroups.com/), or for an extended introduction to the project, [read the about page](https://bristolsocialgroups.com/about).
A website listing social groups in Bristol, UK. [View the live site](https://bristolsocialgroups.com/). For more information about this project, [read the about page](https://bristolsocialgroups.com/about).

The pattern library for this project [is hosted with Storybook](https://jhancock532.github.io/bristol-social-groups/?path=/docs/getting-started--docs).

There are a few AI code generation helpers, for creating new groups and generating Storybook documentation. These require an Anthropic API key, and usually cost around 0.5 - 3 cents to run.

## Documentation Index

- [Getting started](https://github.com/jhancock532/bristol-social-groups/blob/main/documentation/getting-started.md) - a guide for developers new to the project to get up and running.
Expand Down
35 changes: 35 additions & 0 deletions data/groups/drawing-club-bristol/details.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "Drawing Club Bristol",
"slug": "drawing-club-bristol",
"description": "Friendly & informal art club! Weekly meets in Bristol city centre (circa 2015). No fees.",
"details": "",
"tags": ["art", "drawing"],
"type": "Regular",
"events": [
{
"name": "Weekly Drawing Club",
"details": "",
"time": {
"frequency": "Weekly",
"weekday": "Tuesday",
"start": "Jan 1, 1970 18:00",
"end": "Jan 1, 1970 22:00",
"details": ""
},
"location": {
"address": "The Robin Hood, 56 Saint Michael's Hill, BS2 8DX",
"latitude": "51.458572",
"longitude": "-2.599590",
"googleMapsLink": "https://maps.app.goo.gl/E2kTQuDEmiaqhtr19"
},
"cost": {
"sessionPrice": 0,
"details": "No fees"
},
"booking": {
"required": false
},
"url": "https://www.eventbrite.co.uk/e/drawing-club-bristol-free-central-tickets-1013863301427"
}
]
}
2 changes: 1 addition & 1 deletion documentation/development-ethos.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ No guarantee is made for the accuracy of data or for the long term maintenance o

I've included direct links to the groups that host the events, so you can access the latest information about the event and contact the organizers yourself if you have further questions. These links should hopefully boost the search rankings of the individual groups and help them grow.

To reduce carbon emissions and ensure a speedy loading experience, use of images or heavyweight JavaScript is avoided where possible. The OpenStreetMap map widget loads map tile images which are somewhat weighty - this component is only loaded when the user views it, and not bundled within all pages.
To reduce carbon emissions and ensure a speedy loading experience, use of images or heavyweight JavaScript is avoided where possible. The OpenStreetMap map widget loads map tile images which are somewhat weighty - this component is only loaded when the user views it, and not bundled within all pages. I intend to replace the google form widgets with a lightweight alternative.
14 changes: 3 additions & 11 deletions documentation/development-roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Priority features

Search feature.
Search feature (a WIP branch has been created for this)

Create a mailing list where users can sign up to hear about more events in the future. This can be a google form to start with. Emails are sent out once a month or once every two months, depending on the amount of groups added.

Expand All @@ -14,6 +14,8 @@ Add accessibility aria labels or suitable titles to announce the icons in the Gr

Add tests for the filters combined with the map listing component and card feed listing component

Consider and implement a low cost / free alternative to google forms for submitting groups, reducing the carbon impact of the google form pages. Use AI tool such as Townie for this?

## Nice to have

Add nice SVG illustrations that show up at the top of the page for certain group filter options, e.g. fitness or boardgames. Consider something along the lines of https://www.toools.design/free-open-source-illustrations. Keep the image size small so that the page loads quickly & there's less carbon emissions.
Expand All @@ -29,13 +31,3 @@ The font class should live in a seperate file to `_app.tsx` and then get importe
Refactor every text content <a> link to the new <Link> component

Add more styling documentation in `styling.md`

## Potential features needing refinement

Add a star ranking system for how well groups listed follow the aims of the site. Could be controversial and hard to maintain, ideally should be set up with a simple scoring system with clearly defined rules.

## Moonshot features

- Create custom map tiles with a green theme for the in house map component.

- Consider and implement a low cost / free alternative to google forms for submitting groups, reducing the carbon impact of the google form pages.
26 changes: 18 additions & 8 deletions documentation/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@

Welcome! This is a guide for developers who would like to run the project locally in development mode.

After cloning the repository to your machine, make sure you have the appropriate Node version installed.
After cloning the repository to your machine, make sure you have Node version 20 installed.

I'd recommend that you use command line tools such as [fnm](https://github.com/Schniz/fnm) (preferred) or [nvm](https://www.freecodecamp.org/news/node-version-manager-nvm-install-guide/) to switch your node version to the current version used in the repository.

Once you've installed one of the above, `cd` into this projects root directory and run either of the following:
You can use command line tools such as [fnm](https://github.com/Schniz/fnm) or [nvm](https://www.freecodecamp.org/news/node-version-manager-nvm-install-guide/) to switch your node version to the current version used in the repository automatically, by opening a terminal in this projects root directory and running either of the following:

```bash
fnm use
Expand Down Expand Up @@ -36,11 +34,11 @@ Components used in this project are documented in a Storybook instance. To run s

`npm run storybook`

To run automated smoke and accessibility tests over the Storybook stories, open a new terminal window. Then run `npm run test-storybook`.
To run automated smoke and accessibility tests over the Storybook stories, open a new terminal window while Storybook is running. Run the command `npm run test-storybook`.

## Running Playwright tests
## Running visual regression tests

The Playwright tests for this repository are based on the Storybook instance, so make sure this is running first. There are different methods of setting up your environment depending on the device you are using:
The Playwright tests for this repository are based on the Storybook instance, please ensure this instance is running first. There are different methods of setting up your environment depending on the device you are using:

### Linux

Expand All @@ -50,10 +48,22 @@ Edit your `.env` file so that the `PLAYWRIGHT_BASE_URL=http://localhost:6006`

Edit your `.env` file so that the `PLAYWRIGHT_BASE_URL=http://host.docker.internal:6006`

Run `npm run docker-playwright` to enter a docker container. This means that CI and you will have the same operating system while testing visual regression, standardising font rendering and other small differences.
Run `npm run docker-playwright` to enter a docker container. This is required so that you will have the same operating system while testing visual regression as GitHub CI, standardising font rendering and other small differences that otherwise cause snapshots to fail.

### Test commands

You can now run tests using `npm run playwright`

To update snapshots, run `npm run playwright -- --update-snapshots`. If you just want to update one component, use `npm run playwright -- header --update-snapshots` and only the name of the component specified will be run.

## Automations

This project includes scripts to help kick start work on repetitive tasks.

`npm run new-component` - creates a new React component with the specified name, including boilerplate files for that component. Requires no API key.

If you have an Anthropic API key, you can run further automated code generators. Set the `ANTHROPIC_API_KEY=` in your `.env` file, then in the project root, try running the following commands.

`npm run new-group` - creates a new group folder and `details.json` file with the information that you pass into it. You'll be prompted for the slug of the group and then there's a prompt for adding extra information.

`npm run react-to-storybook` - converts a specified React component into a Storybook story automatically.
14 changes: 2 additions & 12 deletions documentation/groups-to-add.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
# Groups to add

The following is a list of groups still to be added to the site.
The following is a list of groups still to be added to the site. These groups are yet to be triaged for suitability, or are otherwise hosted on Meetup / existing services where they can be discovered.

---

Board games with https://northstreetgames.com/ - many weekly events run by this group at low cost, high priority to add

Board game social group - https://www.meetup.com/board-game-social/

ANimation screening and sharing meetup - https://www.instagram.com/animinspo_bristol/

Women's Craft workshops at the Arnolfini, free, fridays 11am to 1pm

Writing meetup group - https://www.meetup.com/bristol-writing-meetup-group/
Expand All @@ -20,12 +16,6 @@ Westbury Harriers - Mix of social / competitive runs - https://groups.runtogethe

A monthly subscription service to access social events as part of an umbrella group - https://www.socialheroesadventureclub.co.uk/ - this may not quite align with the low / no cost as the monthly membership fee is £22, however if this allows access to multiple events it might be worthwhile. Needs some research

Lots of dancing groups

Lots of life drawing events - how social are these events however? Are they a good fit for this site?
Lots of dancing groups!

Bristol Mind https://bristolmind.org.uk/support_type/community-social-activities/

## Vetoed groups

Generic fitness / yoga classes where the group doesn't hang out for a chat afterwards
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"test-storybook": "test-storybook",
"docker-playwright": "docker run --rm -p 9323:9323 -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.46.0-jammy /bin/bash -c 'npm i; /bin/bash'",
"playwright": "npx playwright test",
"new-group": "node scripts/new-group.mjs",
"new-component": "node scripts/new-component.js",
"react-to-storybook": "node scripts/react-to-storybook.mjs",
"generate-visual-tests": "node scripts/generate-visual-tests.mjs"
Expand Down
108 changes: 108 additions & 0 deletions scripts/new-group.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/* eslint-disable no-console */
import Anthropic from '@anthropic-ai/sdk';
import fs from 'fs';
import readline from 'readline';
import path from 'path';
import { config } from 'dotenv';
import { logAnthropicAPICost } from './utils.mjs';

config();

const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});

// Array of representative groups to use as examples for the AI
const EXAMPLE_GROUP_SLUGS = [
'chance-and-counters',
'girls-who-walk-bristol',
'rebel-book-club',
'super-miner-battle-farm',
'ride-bristol',
];

const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});

async function loadExampleGroups() {
const exampleGroups = [];
for (const slug of EXAMPLE_GROUP_SLUGS) {
const groupPath = path.join('data/groups', slug, 'details.json');
if (fs.existsSync(groupPath)) {
const groupData = fs.readFileSync(groupPath, 'utf-8');
exampleGroups.push(JSON.parse(groupData));
}
}
return exampleGroups;
}

async function generateGroupDetails(groupSlug, userInput, exampleGroups) {
const msg = await anthropic.messages.create({
model: process.env.ANTHROPIC_MODEL,
max_tokens: 1000,
system: `You are a programming assistant that generates group detail JSON files based on a group slug and optional user input. You return only valid JSON, and make no other comments. Use the following example groups as context for how your output should be formatted: \n${JSON.stringify(exampleGroups)}\n Return a JSON response to the user's input, only including fields that match the above examples.`,
messages: [
{
role: 'user',
content: `Create a group details object for the group with the URL slug of "${groupSlug}". Additional information: ${userInput}`,
},
],
});

logAnthropicAPICost(msg.usage, process.env.ANTHROPIC_MODEL);

return msg.content[0].text;
}

async function createGroup() {
const groupSlug = await new Promise((resolve) => {
rl.question(
'\x1b[1mEnter the group name as a slug, e.g. "bristol-hot-air-balloonists" :\x1b[0m ',
resolve,
);
});

const groupFolderPath = path.join('data/groups', groupSlug);

if (fs.existsSync(groupFolderPath)) {
console.error(
`The group you've specified "${groupSlug}" already exists.`,
);
rl.close();
process.exit(1);
}

const userInput = await new Promise((resolve) => {
rl.question(
'\x1b[1mEnter any additional information for the AI (optional):\x1b[0m ',
resolve,
);
});

try {
const exampleGroups = await loadExampleGroups();
const groupDetails = await generateGroupDetails(
groupSlug,
userInput,
exampleGroups,
);

const groupDetailsFilePath = path.join(groupFolderPath, 'details.json');

fs.mkdirSync(groupFolderPath, {
recursive: true,
});

fs.writeFileSync(groupDetailsFilePath, groupDetails);

console.log(`\n\x1b[32mGroup created successfully!\x1b[0m\n`);
} catch (error) {
console.error('Error creating group:', error);
}

rl.close();
}

createGroup();
29 changes: 5 additions & 24 deletions scripts/react-to-storybook.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,18 @@ import fs from 'fs';
import readline from 'readline';
import path from 'path';
import { config } from 'dotenv';
import { toCamelCase, readFile, calculateUsageCost } from './utils.mjs';
import { toCamelCase, readFile, logAnthropicAPICost } from './utils.mjs';

config();

// Define the model constant here
// Options: 'Claude 3 Haiku', 'Claude 3.5 Sonnet', 'Claude 3 Opus'
const ANTHROPIC_MODEL = 'Claude 3 Haiku';

const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});

const anthropic = new Anthropic({});

const exampleDocumentation= `
const exampleDocumentation = `
--- REACT COMPONENT ---
import React from 'react';
import Link from 'next/link';
Expand Down Expand Up @@ -296,7 +292,6 @@ export const Default: Story = {
const baseComponentsDirectoryName = 'src/components';

rl.question('\x1b[1mEnter a component name:\x1b[0m ', async (componentName) => {

const camelCaseComponentName = toCamelCase(componentName);

const outputFilePath = `src/components/${camelCaseComponentName}/claude.${camelCaseComponentName}.stories.ts`;
Expand All @@ -312,25 +307,12 @@ rl.question('\x1b[1mEnter a component name:\x1b[0m ', async (componentName) => {
const reactComponentCode = readFile(inputFilePath);
const mocksCode = readFile(inputMocksFilePath);
console.log(
`\x1b[3mConverting React component to Storybook documentation using ${ANTHROPIC_MODEL}...\x1b[0m`,
`\x1b[3mConverting React component to Storybook documentation...\x1b[0m`,
);

let model;
switch (ANTHROPIC_MODEL) {
case 'Claude 3.5 Sonnet':
model = 'claude-3-5-sonnet-20240620';
break;
case 'Claude 3 Opus':
model = 'claude-3-opus-20240229';
break;
case 'Claude 3 Haiku':
default:
model = 'claude-3-haiku-20240307';
}

// eslint-disable-next-line no-await-in-loop
const msg = await anthropic.messages.create({
model: model,
model: process.env.ANTHROPIC_MODEL,
system: `Here are some examples of Storybook documentation of React components.
${exampleDocumentation}
Here are some mock constants for the Storybook stories, as found in @stories/mocks:
Expand All @@ -350,8 +332,7 @@ rl.question('\x1b[1mEnter a component name:\x1b[0m ', async (componentName) => {
}
});

calculateUsageCost(msg.usage, ANTHROPIC_MODEL);
logAnthropicAPICost(msg.usage, process.env.ANTHROPIC_MODEL);

rl.close();
});

Loading

0 comments on commit 3da580b

Please sign in to comment.