├── .npmrc ├── .github ├── CODEOWNERS └── renovate.json ├── src ├── summarizer-app │ ├── memory.json │ ├── auth.yaml │ ├── config.yaml │ ├── articles │ │ ├── tech_stocks.txt │ │ ├── dummy_transcript.txt │ │ └── 2.txt │ ├── script.js │ └── summarizer.py ├── views │ ├── Gallery │ │ ├── index.ts │ │ ├── Gallery.tsx │ │ └── components │ │ │ └── HeroBanner.tsx │ ├── Landing │ │ ├── index.ts │ │ └── Landing.tsx │ └── components │ │ ├── Tags.tsx │ │ ├── NavBar.tsx │ │ └── ArticleSmall.tsx ├── pages │ ├── index.tsx │ ├── api │ │ ├── disable-draft.ts │ │ └── draft.ts │ ├── studio │ │ └── [[...index]].tsx │ ├── gallery.tsx │ └── _app.tsx ├── utils │ └── index.ts ├── content.d.ts ├── schemas │ ├── index.ts │ ├── article.ts │ └── blockContent.ts ├── lib │ ├── sanity.links.ts │ ├── sanity.image.ts │ ├── sanity.client.ts │ ├── sanity.queries.ts │ └── sanity.api.ts ├── components │ ├── PreviewProvider.tsx │ ├── Container.tsx │ ├── sanity.svg │ ├── nextjs.svg │ └── Welcome.tsx └── styles │ └── global.css ├── public └── assets │ ├── darkmode_ss.png │ └── congress-banner.png ├── next.config.mjs ├── netlify.toml ├── next-env.d.ts ├── .env.local.example ├── .eslintrc.json ├── sanity.cli.ts ├── .gitignore ├── tsconfig.json ├── package.json ├── README.md └── sanity.config.ts /.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @sanity-io/ecosystem 2 | -------------------------------------------------------------------------------- /src/summarizer-app/memory.json: -------------------------------------------------------------------------------- 1 | { "memory": [] } 2 | -------------------------------------------------------------------------------- /src/views/Gallery/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './Gallery' -------------------------------------------------------------------------------- /src/views/Landing/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './Landing' -------------------------------------------------------------------------------- /src/summarizer-app/auth.yaml: -------------------------------------------------------------------------------- 1 | openai_api_key: 2 | -------------------------------------------------------------------------------- /public/assets/darkmode_ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pedramhaqiqi/CONGRESS-NEXT/HEAD/public/assets/darkmode_ss.png -------------------------------------------------------------------------------- /public/assets/congress-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pedramhaqiqi/CONGRESS-NEXT/HEAD/public/assets/congress-banner.png -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const config = { 3 | images: { remotePatterns: [{ hostname: 'cdn.sanity.io' }] }, 4 | } 5 | 6 | export default config 7 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | 2 | import Landing from '~/views/Landing' 3 | 4 | 5 | const LandingPage = (props): JSX.Element => { 6 | return 7 | } 8 | 9 | export default LandingPage 10 | -------------------------------------------------------------------------------- /src/summarizer-app/config.yaml: -------------------------------------------------------------------------------- 1 | settings: 2 | dev: true 3 | dev_model: gpt-3.5-turbo-1106 4 | prod_model: gpt-4-1106-preview 5 | image_model: dall-e-3 6 | max_tokens: 100 7 | temperature: 0 8 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [template] 2 | incoming-hooks = ["Sanity"] 3 | 4 | [template.environment] 5 | NEXT_PUBLIC_SANITY_PROJECT_ID="Your Sanity Project Id" 6 | NEXT_PUBLIC_SANITY_DATASET="Your Sanity Dataset" 7 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export function formatDate(date: string) { 2 | return new Date(date).toLocaleDateString('en-US', { 3 | month: 'long', 4 | day: 'numeric', 5 | year: 'numeric', 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Check the readme: https://github.com/sanity-io/renovate-presets/blob/main/ecosystem/README.md", 3 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 4 | "extends": ["github>sanity-io/renovate-config:starter-template"] 5 | } 6 | -------------------------------------------------------------------------------- /src/pages/api/disable-draft.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from 'next' 2 | 3 | export default function disable( 4 | req: NextApiRequest, 5 | res: NextApiResponse, 6 | ) { 7 | res.setDraftMode({ enable: false }) 8 | res.writeHead(307, { Location: '/' }) 9 | res.end() 10 | } 11 | -------------------------------------------------------------------------------- /src/content.d.ts: -------------------------------------------------------------------------------- 1 | export interface Article { 2 | _type: 'post' 3 | _id: string 4 | _createdAt: string 5 | topic: string 6 | slug: string 7 | tags: string[] 8 | image: string 9 | mainImage: string 10 | one_line_summary: string 11 | four_line_summary: string 12 | date: string 13 | } -------------------------------------------------------------------------------- /src/schemas/index.ts: -------------------------------------------------------------------------------- 1 | import { SchemaTypeDefinition } from 'sanity' 2 | 3 | import blockContent from './blockContent' 4 | import article from './article' 5 | 6 | export const schemaTypes = [article, blockContent] 7 | export const schema: { types: SchemaTypeDefinition[] } = { 8 | types: [article, blockContent], 9 | } 10 | -------------------------------------------------------------------------------- /src/lib/sanity.links.ts: -------------------------------------------------------------------------------- 1 | export function resolveHref( 2 | documentType?: string, 3 | slug?: string, 4 | ): string | undefined { 5 | switch (documentType) { 6 | case 'home': 7 | return '/' 8 | case 'page': 9 | return slug ? `/${slug}` : undefined 10 | case 'project': 11 | return slug ? `/projects/${slug}` : undefined 12 | default: 13 | console.warn('Invalid document type:', documentType) 14 | return undefined 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.env.local.example: -------------------------------------------------------------------------------- 1 | # Defaults, used by ./intro-template and can be deleted if the component is removed 2 | NEXT_PUBLIC_VERCEL_GIT_REPO_OWNER="sanity-io" 3 | NEXT_PUBLIC_VERCEL_GIT_PROVIDER="github" 4 | NEXT_PUBLIC_VERCEL_GIT_REPO_SLUG="template-nextjs-clean" 5 | 6 | # Required, find them on https://manage.sanity.io 7 | NEXT_PUBLIC_SANITY_PROJECT_ID= 8 | NEXT_PUBLIC_SANITY_DATASET= 9 | # see https://www.sanity.io/docs/api-versioning for how versioning works 10 | NEXT_PUBLIC_SANITY_API_VERSION="2022-11-28" -------------------------------------------------------------------------------- /src/components/PreviewProvider.tsx: -------------------------------------------------------------------------------- 1 | import { LiveQueryProvider } from 'next-sanity/preview' 2 | import { useMemo } from 'react' 3 | 4 | import { getClient } from '~/lib/sanity.client' 5 | 6 | export default function PreviewProvider({ 7 | children, 8 | token, 9 | }: { 10 | children: React.ReactNode 11 | token: string 12 | }) { 13 | const client = useMemo(() => getClient({ token }), [token]) 14 | return ( 15 | 16 | {children} 17 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /src/pages/studio/[[...index]].tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import { NextStudio } from 'next-sanity/studio' 3 | import { metadata } from 'next-sanity/studio/metadata' 4 | import config from 'sanity.config' 5 | 6 | export default function StudioPage() { 7 | return ( 8 | <> 9 | 10 | {Object.entries(metadata).map(([key, value]) => ( 11 | 12 | ))} 13 | 14 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/sanity.image.ts: -------------------------------------------------------------------------------- 1 | import createImageUrlBuilder from '@sanity/image-url' 2 | import type { Image } from 'sanity' 3 | 4 | import { dataset, projectId } from '~/lib/sanity.api' 5 | 6 | const imageBuilder = createImageUrlBuilder({ 7 | projectId: projectId || '', 8 | dataset: dataset || '', 9 | }) 10 | 11 | export const urlForImage = (source: Image) => { 12 | // Ensure that source image contains a valid reference 13 | if (!source?.asset?._ref) { 14 | return undefined 15 | } 16 | 17 | return imageBuilder?.image(source).auto('format') 18 | } 19 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "root": true, 4 | "extends": [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/recommended" 7 | ], 8 | "parser": "@typescript-eslint/parser", 9 | "parserOptions": { "project": ["./tsconfig.json"] }, 10 | "plugins": [ 11 | "@typescript-eslint" 12 | ], 13 | "rules": { 14 | "@typescript-eslint/strict-boolean-expressions": [ 15 | 2, 16 | { 17 | "allowString" : false, 18 | "allowNumber" : false 19 | } 20 | ] 21 | }, 22 | "ignorePatterns": ["src/**/*.test.ts", "src/frontend/generated/*"] 23 | } -------------------------------------------------------------------------------- /sanity.cli.ts: -------------------------------------------------------------------------------- 1 | import { loadEnvConfig } from '@next/env' 2 | import { defineCliConfig } from 'sanity/cli' 3 | 4 | const dev = process.env.NODE_ENV !== 'production' 5 | loadEnvConfig(__dirname, dev, { info: () => null, error: console.error }) 6 | 7 | // @TODO report top-level await bug 8 | // Using a dynamic import here as `loadEnvConfig` needs to run before this file is loaded 9 | // const { projectId, dataset } = await import('lib/sanity.api') 10 | const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID 11 | const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET 12 | 13 | export default defineCliConfig({ api: { projectId, dataset } }) 14 | -------------------------------------------------------------------------------- /src/pages/gallery.tsx: -------------------------------------------------------------------------------- 1 | import { GetStaticProps, GetServerSideProps } from "next" 2 | 3 | import { Article } from "~/content" 4 | import { readToken } from "~/lib/sanity.api" 5 | import { getClient } from "~/lib/sanity.client" 6 | import { getArticles } from "~/lib/sanity.queries" 7 | import Gallery from "~/views/Gallery" 8 | 9 | export async function getServerSideProps() { 10 | const client = getClient({ token: readToken }) 11 | const posts = await getArticles(client) 12 | return { 13 | props: { 14 | posts, 15 | }, 16 | } 17 | } 18 | 19 | 20 | const GalleryPage = (props): JSX.Element => { 21 | return 22 | } 23 | 24 | export default GalleryPage -------------------------------------------------------------------------------- /src/views/components/Tags.tsx: -------------------------------------------------------------------------------- 1 | import { HStack, SpaceProps, Tag } from "@chakra-ui/react" 2 | import { color } from "framer-motion" 3 | 4 | interface Tags { 5 | tags: Array 6 | color: string 7 | marginTop?: SpaceProps['marginTop'] 8 | } 9 | 10 | const ArticleTags: React.FC = (props) => { 11 | return ( 12 | 13 | {props.tags.map((tag) => { 14 | return ( 15 | 16 | {tag} 17 | 18 | ) 19 | })} 20 | 21 | ) 22 | } 23 | 24 | export default ArticleTags -------------------------------------------------------------------------------- /src/lib/sanity.client.ts: -------------------------------------------------------------------------------- 1 | import { createClient, type SanityClient } from 'next-sanity' 2 | 3 | import { apiVersion, dataset, projectId, useCdn } from '~/lib/sanity.api' 4 | 5 | export function getClient(preview?: { token: string }): SanityClient { 6 | const client = createClient({ 7 | projectId, 8 | dataset, 9 | apiVersion, 10 | useCdn, 11 | perspective: 'published', 12 | }) 13 | if (preview) { 14 | if (!preview.token) { 15 | throw new Error('You must provide a token to preview drafts') 16 | } 17 | return client.withConfig({ 18 | token: preview.token, 19 | useCdn: false, 20 | ignoreBrowserTokenWarning: true, 21 | perspective: 'previewDrafts', 22 | }) 23 | } 24 | return client 25 | } 26 | -------------------------------------------------------------------------------- /src/lib/sanity.queries.ts: -------------------------------------------------------------------------------- 1 | import groq from 'groq' 2 | import { type SanityClient } from 'next-sanity' 3 | 4 | import { Article } from '~/content' 5 | 6 | const articleBySlugQuery = groq` 7 | *[_type == "article" && slug.current == $slug][0]{ 8 | ..., 9 | } 10 | ` 11 | 12 | const articlesQuery = groq` 13 | *[_type == "article"]{ 14 | ..., 15 | "image": image.asset->url 16 | } | order(_createdAt desc) 17 | ` 18 | 19 | export async function getArticle( 20 | client: SanityClient, 21 | slug: string, 22 | ): Promise
{ 23 | return await client.fetch(articleBySlugQuery, { 24 | slug, 25 | }) 26 | } 27 | 28 | export async function getArticles(client: SanityClient): Promise { 29 | return await client.fetch(articlesQuery) 30 | } 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | .vscode 4 | 5 | # dependencies 6 | /node_modules 7 | /studio/node_modules 8 | /.pnp 9 | .pnp.js 10 | 11 | # testing 12 | /coverage 13 | 14 | # next.js 15 | /.next/ 16 | /out/ 17 | 18 | # production 19 | /build 20 | /studio/dist 21 | 22 | # misc 23 | .DS_Store 24 | *.pem 25 | .vscode 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # local env files 34 | .env*.local 35 | 36 | # vercel 37 | .vercel 38 | 39 | # IntelliJ 40 | .idea 41 | *.iml 42 | 43 | # typescript 44 | *.tsbuildinfo 45 | 46 | # Env files created by scripts for working locally 47 | .env 48 | studio/.env.development 49 | src/summarizer-app/auth.yaml 50 | -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '~/styles/global.css' 2 | 3 | import { ChakraProvider, theme } from '@chakra-ui/react' 4 | import type { AppProps } from 'next/app' 5 | import Head from 'next/head' 6 | 7 | export interface SharedPageProps { 8 | draftMode: boolean 9 | token: string 10 | } 11 | 12 | 13 | export default function App({ 14 | Component, 15 | pageProps, 16 | }: AppProps) { 17 | 18 | return ( 19 | <> 20 | 21 | 25 | CONGRESS 26 | 27 | 28 | 29 | 30 | 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "ES2017", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": false, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "incremental": true, 12 | "esModuleInterop": true, 13 | "module": "esnext", 14 | "moduleResolution": "node", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "jsx": "preserve", 18 | "plugins": [ 19 | { 20 | "name": "next" 21 | } 22 | ], 23 | "paths": { 24 | "~/*": ["./src/*"] 25 | } 26 | }, 27 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 28 | "exclude": ["node_modules"] 29 | } 30 | -------------------------------------------------------------------------------- /src/views/components/NavBar.tsx: -------------------------------------------------------------------------------- 1 | import { MoonIcon, SunIcon } from "@chakra-ui/icons"; 2 | import { 3 | Box, 4 | Button, 5 | ChakraProvider, 6 | Flex, 7 | Link, 8 | Stack, 9 | useColorMode, 10 | useColorModeValue, 11 | useDisclosure 12 | } from "@chakra-ui/react"; 13 | import { ReactNode } from "react"; 14 | 15 | 16 | export default function NavBar() { 17 | const { colorMode, toggleColorMode } = useColorMode(); 18 | return ( 19 | <> 20 | 21 | 22 | 23 | co:ngress 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /src/views/Gallery/Gallery.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | ChakraProvider, 3 | Container, 4 | Divider, 5 | Heading, 6 | Wrap 7 | } from '@chakra-ui/react' 8 | 9 | import { Article } from '~/content' 10 | import NavBar from '../components/NavBar' 11 | import RecentHearing from './components/HeroBanner' 12 | import ArticleTags from '../components/Tags' 13 | import SmallArticle from '../components/ArticleSmall' 14 | 15 | const Gallery = (props) => { 16 | const { posts } = props 17 | const mostRecent: Article = posts?.[0] 18 | return ( 19 | 20 | 21 | 22 | 23 | 24 | Latest Hearings: 25 | 26 | 27 | 28 | {posts.map((article) => ( 29 | 32 | ))} 33 | 34 | 35 | 36 | ) 37 | } 38 | 39 | export default Gallery 40 | -------------------------------------------------------------------------------- /src/components/Container.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | 3 | export default function Container({ children }: { children: React.ReactNode }) { 4 | return ( 5 |
6 |
7 | 8 | Next.js + Sanity 9 | 10 |
11 |
{children}
12 |
13 |

14 | Made with{' '} 15 | 23 | 29 | {' '} 30 | at Sanity 31 |

32 |
33 |
34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /src/components/sanity.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/components/nextjs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/lib/sanity.api.ts: -------------------------------------------------------------------------------- 1 | export const useCdn = false 2 | 3 | /** 4 | * As this file is reused in several other files, try to keep it lean and small. 5 | * Importing other npm packages here could lead to needlessly increasing the client bundle size, or end up in a server-only function that don't need it. 6 | */ 7 | 8 | export const dataset = assertValue( 9 | process.env.NEXT_PUBLIC_SANITY_DATASET, 10 | 'Missing environment variable: NEXT_PUBLIC_SANITY_DATASET', 11 | ) 12 | 13 | export const projectId = assertValue( 14 | process.env.NEXT_PUBLIC_SANITY_PROJECT_ID, 15 | 'Missing environment variable: NEXT_PUBLIC_SANITY_PROJECT_ID', 16 | ) 17 | 18 | export const readToken = process.env.SANITY_API_READ_TOKEN || '' 19 | 20 | // see https://www.sanity.io/docs/api-versioning for how versioning works 21 | export const apiVersion = 22 | process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2023-06-21' 23 | 24 | // This is the document id used for the preview secret that's stored in your dataset. 25 | // The secret protects against unauthorized access to your draft content and have a lifetime of 60 minutes, to protect against bruteforcing. 26 | export const previewSecretId: `${string}.${string}` = 'preview.secret' 27 | 28 | function assertValue(v: T | undefined, errorMessage: string): T { 29 | if (v === undefined) { 30 | throw new Error(errorMessage) 31 | } 32 | 33 | return v 34 | } 35 | -------------------------------------------------------------------------------- /src/schemas/article.ts: -------------------------------------------------------------------------------- 1 | import { defineField, defineType } from 'sanity' 2 | 3 | export default defineType({ 4 | name: 'article', 5 | title: 'Article', 6 | type: 'document', 7 | fields: [ 8 | defineField({ 9 | name: 'title', 10 | title: 'Title', 11 | type: 'string', 12 | }), 13 | defineField({ 14 | name: 'topic', 15 | title: 'Topic', 16 | type: 'string', 17 | }), 18 | defineField({ 19 | name: 'slug', 20 | title: 'slug', 21 | type: 'string', 22 | }), 23 | defineField({ 24 | name: 'one_line_summary', 25 | title: 'One line summary', 26 | type: 'text', 27 | }), 28 | defineField({ 29 | name: 'four_line_summary', 30 | title: 'Four line summary', 31 | type: 'text', 32 | }), 33 | 34 | defineField({ 35 | name: 'date', 36 | title: 'Date', 37 | type: 'text', 38 | }), 39 | defineField({ 40 | name: 'image', 41 | title: 'Image', 42 | type: 'image', 43 | }), 44 | defineField({ 45 | title: 'tags', 46 | name: 'tags', 47 | type: 'array', 48 | of: [{type: 'string'}] 49 | }), 50 | ], 51 | preview: { 52 | select: { 53 | title: 'title', 54 | author: 'author.name', 55 | media: 'mainImage', 56 | }, 57 | prepare(selection) { 58 | const { author } = selection 59 | return { ...selection, subtitle: author && `by ${author}` } 60 | }, 61 | }, 62 | }) 63 | -------------------------------------------------------------------------------- /src/pages/api/draft.ts: -------------------------------------------------------------------------------- 1 | import type { NextApiRequest, NextApiResponse } from 'next' 2 | import { isValidSecret } from 'sanity-plugin-iframe-pane/is-valid-secret' 3 | 4 | import { previewSecretId, readToken } from '~/lib/sanity.api' 5 | import { getClient } from '~/lib/sanity.client' 6 | 7 | export default async function preview( 8 | req: NextApiRequest, 9 | res: NextApiResponse, 10 | ) { 11 | if (!readToken) { 12 | res.status(500).send('Misconfigured server') 13 | return 14 | } 15 | 16 | const { query } = req 17 | 18 | const secret = typeof query.secret === 'string' ? query.secret : undefined 19 | const slug = typeof query.slug === 'string' ? query.slug : undefined 20 | 21 | if (!secret) { 22 | res.status(401) 23 | res.send('Invalid secret') 24 | return 25 | } 26 | 27 | const authClient = getClient({ token: readToken }).withConfig({ 28 | useCdn: false, 29 | token: readToken, 30 | }) 31 | 32 | // This is the most common way to check for auth, but we encourage you to use your existing auth 33 | // infra to protect your token and securely transmit it to the client 34 | const validSecret = await isValidSecret(authClient, previewSecretId, secret) 35 | if (!validSecret) { 36 | return res.status(401).send('Invalid secret') 37 | } 38 | 39 | if (slug) { 40 | res.setDraftMode({ enable: true }) 41 | res.writeHead(307, { Location: `/post/${slug}` }) 42 | res.end() 43 | return 44 | } 45 | 46 | res.status(404).send('Slug query parameter is required') 47 | res.end() 48 | } 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "build": "next build", 5 | "dev": "next", 6 | "format": "npx prettier --write . --ignore-path .gitignore", 7 | "lint": "next lint -- --ignore-path .gitignore", 8 | "lint:fix": "npm run format && npm run lint -- --fix", 9 | "start": "next start", 10 | "type-check": "tsc --noEmit" 11 | }, 12 | "prettier": { 13 | "semi": false, 14 | "singleQuote": true 15 | }, 16 | "dependencies": { 17 | "@chakra-ui/icons": "^2.1.1", 18 | "@chakra-ui/react": "^2.8.2", 19 | "@emotion/react": "^11.11.3", 20 | "@emotion/styled": "^11.11.0", 21 | "@portabletext/react": "3.0.11", 22 | "@sanity/client": "^6.8.6", 23 | "@sanity/demo": "1.0.2", 24 | "@sanity/vision": "3.20.0", 25 | "@tailwindcss/typography": "0.5.10", 26 | "framer-motion": "^10.16.16", 27 | "next": "14.0.3", 28 | "next-sanity": "5.5.10", 29 | "react": "18.2.0", 30 | "react-dom": "18.2.0", 31 | "react-icons": "^4.12.0", 32 | "sanity": "3.20.0", 33 | "sanity-plugin-iframe-pane": "2.6.1", 34 | "styled-components": "6.1.1" 35 | }, 36 | "devDependencies": { 37 | "@types/react": "18.2.37", 38 | "autoprefixer": "10.4.16", 39 | "eslint": "8.53.0", 40 | "eslint-config-next": "14.0.3", 41 | "eslint-plugin-simple-import-sort": "10.0.0", 42 | "postcss": "8.4.31", 43 | "prettier": "3.1.0", 44 | "prettier-plugin-packagejson": "2.4.6", 45 | "prettier-plugin-tailwindcss": "0.5.7", 46 | "tailwindcss": "3.3.5", 47 | "typescript": "5.2.2" 48 | }, 49 | "engines": { 50 | "node": ">=16" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/views/components/ArticleSmall.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Box, 3 | Button, 4 | ChakraProvider, 5 | Heading, 6 | HStack, 7 | Image, 8 | Link, 9 | SpaceProps, 10 | Tag, 11 | Text, 12 | WrapItem, 13 | } from "@chakra-ui/react"; 14 | import React from "react"; 15 | import { Article } from "~/content"; 16 | import ArticleTags from "./Tags"; 17 | 18 | 19 | const SmallArticle = (props: Article) => { 20 | const { topic, tags, date, one_line_summary, image } = props; 21 | return ( 22 | 23 | 24 | 25 | 26 | 27 | some text 38 | 39 | 40 | 41 | 42 | 43 | {topic} 44 | 45 | 46 | 47 | {one_line_summary} 48 | 49 | {/* */} 50 | 51 | 52 | 53 | ); 54 | }; 55 | 56 | export default SmallArticle; 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | vectre-image 2 | 3 |
4 | 5 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 6 | 7 |
8 | 9 | live site 10 | 11 | --- 12 | 13 | ## Hack The Valley 7 🏆 14 | 15 | We are proud to say that CO:NGRESS is University of Toronto's 2022 Hack the Valley 7 First Prize Winner! [DevPost](https://devpost.com/software/co-ngress) 16 | 17 | ## Inspiration 18 | 19 | We were inspired by the fact that a TL:DR is included at the start/end of most news articles, but there was no TL:DR for parliament hearings. News outlets are a great way of keeping track of what's going on in Canada, but news mostly consists of mainstream headlines and is not at all inclusive of all decisions that are being made in our country. We saw the opportunity, and went for it! A TL:DR for parliament hearings! 20 | 21 | ## What it does? 22 | 23 | Our app scrapes proceedings from the House of Commons of Canada and runs it through the Article Summarization feature using Open-AI 24 | 25 | Once summarized, it roughly interprets the topic of the hearing and passes it as a prompt to Dalle3, fetching an AI-generated image, as a small entertainment factor for the user. 26 | Storage is done with headless CMS Sanity 27 | 28 | ## Built with 🛠 29 | 30 | - [React + TypeScript](https://reactjs.org/) (Client Application) 31 | - [Nextjs + Vercel] 32 | - [Sanity Headless CMS] 33 | 34 | ## Contributors ✨ 35 | 36 | - Pedram Meskoub [Linkedin](https://www.linkedin.com/in/pedramhaqiqi/) 37 | - Efkan Serhat Goktepe [Linkedin](https://www.linkedin.com/in/serhatgoktepe/) 38 | - Nikhil lakhwani [Linkedin](https://www.linkedin.com/in/nlakhwani/) 39 | - Daniyal Iqbal [Linkedin](https://www.linkedin.com/in/daniyal-iqbal-726a69219/) 40 | - Shahin Jowkar Dris [Linkedin](https://www.linkedin.com/in/shahinjowkar) 41 | 42 | ## Project Screenshot 📸 43 | 44 | -------------------------------------------------------------------------------- /src/schemas/blockContent.ts: -------------------------------------------------------------------------------- 1 | import { defineArrayMember, defineType } from 'sanity' 2 | 3 | /** 4 | * This is the schema definition for the rich text fields used for 5 | * for this blog studio. When you import it in schemas.js it can be 6 | * reused in other parts of the studio with: 7 | * { 8 | * name: 'someName', 9 | * title: 'Some title', 10 | * type: 'blockContent' 11 | * } 12 | */ 13 | export default defineType({ 14 | title: 'Block Content', 15 | name: 'blockContent', 16 | type: 'array', 17 | of: [ 18 | defineArrayMember({ 19 | title: 'Block', 20 | type: 'block', 21 | // Styles let you set what your user can mark up blocks with. These 22 | // correspond with HTML tags, but you can set any title or value 23 | // you want and decide how you want to deal with it where you want to 24 | // use your content. 25 | styles: [ 26 | { title: 'Normal', value: 'normal' }, 27 | { title: 'H1', value: 'h1' }, 28 | { title: 'H2', value: 'h2' }, 29 | { title: 'H3', value: 'h3' }, 30 | { title: 'H4', value: 'h4' }, 31 | { title: 'Quote', value: 'blockquote' }, 32 | ], 33 | lists: [{ title: 'Bullet', value: 'bullet' }], 34 | // Marks let you mark up inline text in the block editor. 35 | marks: { 36 | // Decorators usually describe a single property – e.g. a typographic 37 | // preference or highlighting by editors. 38 | decorators: [ 39 | { title: 'Strong', value: 'strong' }, 40 | { title: 'Emphasis', value: 'em' }, 41 | ], 42 | // Annotations can be any object structure – e.g. a link or a footnote. 43 | annotations: [ 44 | { 45 | title: 'URL', 46 | name: 'link', 47 | type: 'object', 48 | fields: [ 49 | { 50 | title: 'URL', 51 | name: 'href', 52 | type: 'url', 53 | }, 54 | ], 55 | }, 56 | ], 57 | }, 58 | }), 59 | ], 60 | }) 61 | -------------------------------------------------------------------------------- /src/summarizer-app/articles/tech_stocks.txt: -------------------------------------------------------------------------------- 1 | 2 | New York 3 | CNN 4 | — 5 | US stocks soared higher on Thursday morning after a strong earnings report from tech giant Nvidia bolstered investor optimism on Wall Street. 6 | 7 | The blue-chip Dow Jones Industrial Average was up 278 points, or 0.7%. The S&P 500 gained 1.6%, and the tech-heavy Nasdaq Composite was 2.4% higher. 8 | 9 | The S&P 500 hit a record high during morning trading on the pop in big tech and the Nasdaq is tracking towards a new all-time high as well. 10 | 11 | Nvidia (NVDA), one of the largest companies on Wall Street, led gains after reporting extraordinary earnings growth, fueled by the artificial intelligence boom. 12 | 13 | Profits of the chipmaker grew to nearly $12.3 billion in the three months ended January 28 — up from $1.4 billion in the year-ago quarter, a gain of 769% year-over-year and even stronger growth than Wall Street analysts had expected. That result helped bring the company’s full-year profits up more than 580% from the year earlier. 14 | 15 | Nvidia CEO Jensen Huang said in a statement on Wednesday evening that generative AI has now “hit the tipping point.” 16 | 17 | “Demand is surging worldwide across companies, industries and nations,” he added. 18 | 19 | Shares of the stock were 15.3% higher in morning trading, a reversal from earlier in the week when the company logged its worst day since October. 20 | 21 | Other chipmakers benefited from Nvidia’s good news. Shares of AMD (AMD) were 9.5% higher on Thursday morning and Microsoft (MSFT) was 2% higher. 22 | 23 | The Nvidia boost has put Federal Reserve worries on the backburner for now, but traders got some disappointing news on Wednesday. 24 | 25 | Fed officials continue to worry that inflation could stay stubbornly high during their policy meeting last month, minutes released yesterday afternoon showed. That could keep interest rates at 23-year high for longer than previously expected, affecting Americans’ borrowing costs on everything from car loans to mortgages. 26 | 27 | Traders now largely expect the Fed to begin cutting rates in June or July, rather than at its May policy meeting, according to the CME FedWatch Tool. -------------------------------------------------------------------------------- /src/components/Welcome.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image' 2 | import Link from 'next/link' 3 | 4 | import NextLogo from './nextjs.svg' 5 | import SanityLogo from './sanity.svg' 6 | 7 | export default function Welcome() { 8 | return ( 9 |
10 |
11 |
12 | Next.js Logo 13 | + 14 | Sanity Logo 15 |
16 |
17 |

Next steps

18 |
    19 |
  • 20 |

    Publish a post in your Studio

    21 |

    22 | Visit the Sanity Studio and publish a 23 | new document of type post. 24 |

    25 |
  • 26 |
  • 27 |

    Dive into the documentation

    28 |

    29 | Check out{' '} 30 | 35 | the documentation 36 | {' '} 37 | to learn more about Sanity. 38 |

    39 |
  • 40 |
  • 41 |

    Join the Sanity Community

    42 |

    43 | Leverage{' '} 44 | 49 | our awesome community 50 | 51 | , and share tips and discuss! 52 |

    53 |
  • 54 |
55 |
56 |
57 | ) 58 | } 59 | -------------------------------------------------------------------------------- /sanity.config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This config is used to set up Sanity Studio that's mounted on the `/pages/studio/[[...index]].tsx` route 3 | */ 4 | 5 | import { visionTool } from '@sanity/vision' 6 | import { defineConfig } from 'sanity' 7 | import { deskTool } from 'sanity/desk' 8 | import { 9 | defineUrlResolver, 10 | Iframe, 11 | IframeOptions, 12 | } from 'sanity-plugin-iframe-pane' 13 | import { previewUrl } from 'sanity-plugin-iframe-pane/preview-url' 14 | 15 | // see https://www.sanity.io/docs/api-versioning for how versioning works 16 | import { 17 | apiVersion, 18 | dataset, 19 | previewSecretId, 20 | projectId, 21 | } from '~/lib/sanity.api' 22 | import { schema } from '~/schemas' 23 | 24 | const iframeOptions = { 25 | url: defineUrlResolver({ 26 | base: '/api/draft', 27 | requiresSlug: ['post'], 28 | }), 29 | urlSecretId: previewSecretId, 30 | reload: { button: true }, 31 | } satisfies IframeOptions 32 | 33 | export default defineConfig({ 34 | basePath: '/studio', 35 | name: 'project-name', 36 | title: 'Project Name', 37 | projectId, 38 | dataset, 39 | //edit schemas in './src/schemas' 40 | schema, 41 | plugins: [ 42 | deskTool({ 43 | // `defaultDocumentNode` is responsible for adding a “Preview” tab to the document pane 44 | // You can add any React component to `S.view.component` and it will be rendered in the pane 45 | // and have access to content in the form in real-time. 46 | // It's part of the Studio's “Structure Builder API” and is documented here: 47 | // https://www.sanity.io/docs/structure-builder-reference 48 | defaultDocumentNode: (S, { schemaType }) => { 49 | return S.document().views([ 50 | // Default form view 51 | S.view.form(), 52 | // Preview 53 | S.view.component(Iframe).options(iframeOptions).title('Preview'), 54 | ]) 55 | }, 56 | }), 57 | // Add the "Open preview" action 58 | previewUrl({ 59 | base: '/api/draft', 60 | requiresSlug: ['post'], 61 | urlSecretId: previewSecretId, 62 | }), 63 | // Vision lets you query your content with GROQ in the studio 64 | // https://www.sanity.io/docs/the-vision-plugin 65 | visionTool({ defaultApiVersion: apiVersion }), 66 | ], 67 | }) 68 | -------------------------------------------------------------------------------- /src/views/Gallery/components/HeroBanner.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Box, 3 | Heading, 4 | Image, 5 | Link, 6 | Text, 7 | } from "@chakra-ui/react"; 8 | import { Article } from "~/content"; 9 | import ArticleTags from "~/views/components/Tags"; 10 | 11 | const RecentHearing = (props: Article) => { 12 | const { topic, one_line_summary, image, tags } = props; 13 | console.log(props); 14 | console.log(image) 15 | return ( 16 | 17 | 23 | 30 | Most Recent Hearing: 31 | 32 | 33 | 38 | {topic} 39 | 40 | 41 | 42 | {one_line_summary} 43 | 44 | 45 | 52 | 58 | 59 | some good alt text 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | ); 80 | }; 81 | 82 | export default RecentHearing; 83 | -------------------------------------------------------------------------------- /src/summarizer-app/script.js: -------------------------------------------------------------------------------- 1 | const util = require('util') 2 | const sanityClient = require('@sanity/client') 3 | const fs = require('fs') 4 | const path = require('path') 5 | 6 | const client = sanityClient.createClient({ 7 | projectId: process.env.SANITY_API_PROJECT_ID, // you can find this in sanity.json or manage.sanity.io 8 | dataset: process.env.SANITY_API_DATASET, // or whatever your dataset is named 9 | token: process.env.SANITY_API_WRITE_TOKEN, // you need a token with write access 10 | apiVersion: '2024-01-01', 11 | useCdn: false, // `false` if you want to ensure fresh data 12 | }) 13 | 14 | const timestamp = Date.now() 15 | const uniqueId = `article_${timestamp}` 16 | const exec = util.promisify(require('child_process').exec) 17 | 18 | async function runPythonScript() { 19 | try { 20 | const { stdout, stderr } = await exec('python3 summarizer.py') 21 | 22 | if (stderr) { 23 | console.error(`stderr: ${stderr}`) 24 | return 1 25 | } 26 | 27 | //read image file and convert to base64 28 | 29 | const jsonObject = JSON.parse(stdout) 30 | const imagePath = path.join('images', `${jsonObject.image_name}.jpg`) 31 | const imageAsset = await client.assets.upload( 32 | 'image', 33 | fs.createReadStream(imagePath), 34 | ) 35 | const mutations = [ 36 | { 37 | createOrReplace: { 38 | _id: uniqueId, 39 | _type: 'article', 40 | title: `${jsonObject.topic}-${jsonObject.date}`, 41 | topic: jsonObject.topic, 42 | image: { 43 | _type: 'image', 44 | asset: { 45 | _type: 'reference', 46 | _ref: imageAsset._id, // Reference the uploaded image asset 47 | }, 48 | }, 49 | one_line_summary: jsonObject.one_sentence_summary, 50 | four_line_summary: jsonObject.four_sentence_summary, 51 | date: jsonObject.date, 52 | tags: jsonObject.tags, 53 | }, 54 | }, 55 | ] 56 | fetch( 57 | `https://${process.env.SANITY_API_PROJECT_ID}.api.sanity.io/v2021-06-07/data/mutate/${process.env.SANITY_API_DATASET}`, 58 | { 59 | method: 'post', 60 | headers: { 61 | 'Content-type': 'application/json', 62 | Authorization: `Bearer ${process.env.SANITY_API_WRITE_TOKEN}`, 63 | }, 64 | body: JSON.stringify({ mutations }), 65 | }, 66 | ) 67 | .then((response) => response.json()) 68 | .then((result) => console.log(result)) 69 | .catch((error) => console.error(error)) 70 | } catch (error) { 71 | console.log(error) 72 | return 1 73 | } 74 | } 75 | 76 | runPythonScript() 77 | -------------------------------------------------------------------------------- /src/views/Landing/Landing.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Box, 3 | Button, 4 | ChakraProvider, 5 | Container, 6 | createIcon, 7 | Heading, 8 | Icon, 9 | Stack, 10 | Text, 11 | useColorModeValue, 12 | } from "@chakra-ui/react"; 13 | import { useRouter } from "next/router"; 14 | 15 | import NavBar from "../components/NavBar"; 16 | 17 | export default function CallToActionWithAnnotation() { 18 | const router = useRouter(); 19 | 20 | return ( 21 | <> 22 | 23 | 24 | 25 | 31 | 36 | The House of Commons
37 | 38 | Via Artificial Intelligence 39 | 40 |
41 | 42 | Bite-size snippets of the most recent legislative matters, to keep 43 | you up to date with the information that matters. Get a better 44 | understanding of the legal system in Canada and more. 45 | 46 | 53 | 65 | 68 | 69 | 77 | 78 | 79 |
80 |
81 |
82 | 83 | ); 84 | } 85 | 86 | const Arrow = createIcon({ 87 | displayName: "Arrow", 88 | viewBox: "0 0 72 24", 89 | path: ( 90 | 96 | ), 97 | }); 98 | -------------------------------------------------------------------------------- /src/summarizer-app/summarizer.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import yaml 3 | import json 4 | import os 5 | import regex as re 6 | import sys 7 | from openai import OpenAI 8 | from datetime import datetime 9 | 10 | client = OpenAI() 11 | 12 | with open('auth.yaml', 'r') as file: 13 | OPENAI_API_KEY = yaml.load(file, Loader=yaml.FullLoader)['openai_api_key'] 14 | with open('config.yaml', 'r') as file: 15 | config = yaml.load(file, Loader=yaml.FullLoader) 16 | 17 | DEV = config['settings']['dev'] 18 | DEV_MODEL = config['settings']['dev_model'] 19 | PROD_MODEL = config['settings']['prod_model'] 20 | IMAGE_MODEL = config['settings']['image_model'] 21 | MAX_TOKENS = config['settings']['max_tokens'] 22 | TEMPERATURE = config['settings']['temperature'] 23 | 24 | session = requests.Session() 25 | session.headers.update({ 26 | "Content-Type": "application/json", 27 | "Authorization": f"Bearer {OPENAI_API_KEY}" 28 | }) 29 | 30 | def yield_files(directory): 31 | """ 32 | Generator function that yields the file paths of all files in a given directory. 33 | 34 | Args: 35 | directory (str): The directory path. 36 | 37 | Yields: 38 | str: The file path of each file in the directory. 39 | """ 40 | for root, dirs, files in os.walk(directory): 41 | for file in files: 42 | yield os.path.join(root, file) 43 | 44 | 45 | def _fetch_title_and_summaries(transcript): 46 | """ 47 | Fetches the title and summaries of a parliament hearing transcript. 48 | 49 | Args: 50 | transcript (str): The transcript of the parliament hearing. 51 | 52 | Returns: 53 | dict: A dictionary containing the topic, one sentence summary, four sentence summary, tags, and date of the hearing. 54 | """ 55 | prompt = f""" 56 | Below is a parliament hearing transcript. Analyze it and provide the following: 57 | 58 | 1) The topic discussed in the hearing in at most 7 words 59 | 2) A one sentence summary of the hearing 60 | 3) A four sentence summary of the hearing 61 | 4) A list of three one-word tags related to the hearing 62 | 5) The date of the hearing in the same format as seen in the transcript 63 | 64 | Ensure your response is only a valid JSON string with keys "topic", "one_sentence_summary", "four_sentence_summary", "tags", and "date". 65 | \"\"\" 66 | {transcript} 67 | \"\"\" 68 | """ 69 | 70 | response = client.chat.completions.create( 71 | model= DEV_MODEL if DEV else PROD_MODEL, 72 | response_format={ "type": "json_object" }, 73 | messages=[ 74 | {"role": "system", "content": "You are a helpful assistant designed to output JSON."}, 75 | {"role": "user", "content": prompt} 76 | ] 77 | ) 78 | 79 | return json.loads(response.choices[0].message.content) 80 | 81 | 82 | def fetch_image_from_title(title): 83 | """ 84 | Fetches an image related to the given title. 85 | 86 | Args: 87 | title (str): The title of the hearing. 88 | 89 | Returns: 90 | str: The URL of the fetched image. 91 | """ 92 | url = "https://api.openai.com/v1/images/generations" 93 | data = { 94 | "prompt": f"Draw me an image related to the topic of {title}. Do not include any text in your image.", 95 | "model": IMAGE_MODEL 96 | } 97 | response = session.post(url, json=data) 98 | return response.json()['data'][0]['url'] 99 | 100 | 101 | def process_files(directory, memory): 102 | """ 103 | Processes the files in a given directory. 104 | Processed one file at a time. 105 | appends the file name to memory.json after processing. 106 | 107 | 108 | Args: 109 | directory (str): The directory path. 110 | memory (dict): The memory dictionary. 111 | 112 | Raises: 113 | FileNotFoundError: If no match is found for a file path. 114 | """ 115 | pattern = re.compile(r"/([^/]+)$") 116 | for file_path in yield_files(directory): 117 | match = pattern.search(file_path) 118 | if not match: 119 | raise FileNotFoundError(f"No match found for {file_path}") 120 | last_part = match.group(1) 121 | if last_part not in memory['memory']: 122 | process_file(file_path, last_part, memory) 123 | break 124 | 125 | def process_file(file_path, last_part, memory): 126 | """ 127 | Processes a single file. 128 | Passes the file to _fetch_title_and_summaries and fetch_image_from_title. 129 | Which get title and summaries from OpenAI and fetches an image from OpenAI. 130 | 131 | Args: 132 | file_path (str): The file path. 133 | last_part (str): The last part of the file path. 134 | memory (dict): The memory dictionary. 135 | """ 136 | with open(file_path, 'r', encoding='utf-8') as article_file: 137 | result = _fetch_title_and_summaries(article_file.read()) 138 | image_url = fetch_image_from_title(result['four_sentence_summary']) 139 | # Script.js reads from stdout 140 | response = requests.get(image_url) 141 | if response.status_code == 200: 142 | #Get Date time for unique image name 143 | date = datetime.now().strftime("%Y%m%d%H%M%S") 144 | with open(f"images/{date}.jpg", "wb") as file: 145 | # Write the content of the response (the image) to the file 146 | file.write(response.content) 147 | result['image_name'] = date 148 | else: 149 | print("Image downloaded failed.") 150 | sys.exit(1) 151 | print(json.dumps(result)) 152 | memory['memory'].append(last_part) 153 | with open('memory.json', 'w') as memory_file: 154 | json.dump(memory, memory_file) 155 | 156 | if __name__ == "__main__": 157 | #Create memory.json with intial value of {memory: []} if it does not exist 158 | if not os.path.exists("memory.json"): 159 | with open('memory.json', 'w') as memory_file: 160 | json.dump({'memory': []}, memory_file) 161 | 162 | # Checks if the files in articles have been processed before, and only processes the new ones 163 | with open('memory.json', 'r', ) as memory_file: 164 | memory = json.load(memory_file) 165 | process_files('articles', memory) 166 | -------------------------------------------------------------------------------- /src/styles/global.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --space-0: 0; 3 | --space-1: 4px; 4 | --space-2: 8px; 5 | --space-3: 12px; 6 | --space-4: 20px; 7 | --space-5: 32px; 8 | --space-6: 52px; 9 | --space-7: 84px; 10 | --space-8: 136px; 11 | --space-9: 220px; 12 | 13 | --font-family-sans: Inter; 14 | --font-family-serif: PT Serif; 15 | --font-family-mono: IMB Plex Mono; 16 | 17 | --font-size-0: 12px; 18 | --font-size-1: 14px; 19 | --font-size-2: 16px; 20 | --font-size-3: 18px; 21 | --font-size-4: 20px; 22 | --font-size-5: 24px; 23 | --font-size-6: 30px; 24 | --font-size-7: 36px; 25 | --font-size-8: 48px; 26 | --font-size-9: 60px; 27 | --font-size-10: 72px; 28 | 29 | --line-height-0: 16px; 30 | --line-height-1: 20px; 31 | --line-height-2: 24px; 32 | --line-height-3: 28px; 33 | --line-height-4: 28px; 34 | --line-height-5: 32px; 35 | --line-height-6: 36px; 36 | --line-height-7: 40px; 37 | --line-height-8: 48px; 38 | --line-height-9: 60px; 39 | --line-height-10: 72px; 40 | --line-height-11: 96px; 41 | --line-height-12: 128px; 42 | 43 | --white: #fff; 44 | --black: #101112; 45 | --gray-200: #ced2d9; 46 | --gray-600: #6e7683; 47 | --blue-600: #1e61cd; 48 | --magenta-100: #f9d7eb; 49 | 50 | --max-width-0: 320px; 51 | --max-width-1: 768px; 52 | } 53 | 54 | html { 55 | font-family: var(--font-family-sans), var(--font-family-serif), sans-serif; 56 | -webkit-text-size-adjust: 100%; 57 | -moz-text-size-adjust: 100%; 58 | text-size-adjust: 100%; 59 | } 60 | 61 | body { 62 | width: 100%; 63 | height: 100%; 64 | margin: 0; 65 | } 66 | 67 | /* Post page */ 68 | .post { 69 | width: 100%; 70 | margin: var(--space-1) 0 var(--space-4); 71 | } 72 | 73 | .post .post__cover, 74 | .post .post__cover--none { 75 | width: 100%; 76 | height: 200px; 77 | -o-object-fit: cover; 78 | object-fit: cover; 79 | } 80 | 81 | .post .post__cover--none { 82 | background: var(--black); 83 | } 84 | 85 | .post .post__container { 86 | padding: 0 var(--space-3); 87 | } 88 | 89 | .post .post__content { 90 | font-family: var(--font-family-serif); 91 | font-weight: 400; 92 | font-size: var(--font-size-4); 93 | line-height: var(--line-height-5); 94 | letter-spacing: -0.02em; 95 | margin-top: var(--space-6); 96 | 97 | /* Targeting tags in PortableText */ 98 | } 99 | 100 | .post .post__content blockquote { 101 | border-left: 5px solid var(--black); 102 | padding-left: var(--space-3); 103 | margin-left: var(--space-4); 104 | } 105 | 106 | .post .post__content a { 107 | color: var(--blue-600); 108 | text-decoration: none; 109 | } 110 | 111 | .post .post__title { 112 | font-family: var(--font-family-sans); 113 | font-size: var(--font-size-7); 114 | line-height: var(--line-height-6); 115 | margin: var(--space-4) 0; 116 | font-weight: 800; 117 | } 118 | 119 | .post .post__excerpt { 120 | font-family: var(--font-family-serif); 121 | font-size: var(--font-size-5); 122 | line-height: var(--line-height-4); 123 | margin-top: 0; 124 | font-weight: 400; 125 | } 126 | 127 | .post .post__date { 128 | font-family: var(--font-family-sans); 129 | font-weight: 600; 130 | font-family: var(--font-family-sans); 131 | font-size: var(--font-size-1); 132 | line-height: var(--line-height-1); 133 | margin-top: var(--space-4); 134 | } 135 | 136 | @media (min-width: 800px) { 137 | .post .post__cover, 138 | .post .post__cover--none { 139 | width: 750px; 140 | height: 380px; 141 | } 142 | 143 | .post .post__title { 144 | font-size: var(--font-size-10); 145 | line-height: var(--line-height-10); 146 | margin: var(--space-6) 0 0; 147 | letter-spacing: -0.025em; 148 | } 149 | 150 | .post .post__excerpt { 151 | font-size: var(--font-size-5); 152 | line-height: var(--line-height-5); 153 | margin-top: var(--space-3); 154 | margin-bottom: var(--space-3); 155 | } 156 | 157 | .post .post__date { 158 | font-size: var(--font-size-3); 159 | line-height: var(--line-height-2); 160 | margin-top: var(--space-0); 161 | } 162 | 163 | .post .post__content { 164 | margin-top: var(--space-7); 165 | } 166 | } 167 | 168 | /* Layout */ 169 | .container { 170 | margin: 0 auto; 171 | } 172 | 173 | main { 174 | margin-top: 45px; 175 | } 176 | 177 | .header { 178 | display: flex; 179 | padding: 0 var(--space-1); 180 | border-bottom: 1px solid #ced2d9; 181 | 182 | z-index: 10; 183 | background: var(--white); 184 | position: fixed; 185 | left: 0; 186 | right: 0; 187 | top: 0; 188 | } 189 | 190 | .header .header__title { 191 | font-weight: 800; 192 | font-size: var(--font-size-3); 193 | line-height: var(--line-height-1); 194 | padding-left: var(--space-2); 195 | margin: var(--space-3) 0; 196 | text-decoration: none; 197 | color: var(--black); 198 | } 199 | 200 | .footer { 201 | display: flex; 202 | justify-content: flex-end; 203 | padding: 0 var(--space-3); 204 | } 205 | 206 | .footer .footer__text { 207 | font-size: var(--font-size-1); 208 | line-height: var(--line-height-1); 209 | display: flex; 210 | align-items: center; 211 | gap: 2px; 212 | } 213 | 214 | @media (min-width: 575px) { 215 | .container { 216 | max-width: var(--max-width-1); 217 | padding: 0 var(--space-4); 218 | } 219 | 220 | main { 221 | margin-top: unset; 222 | } 223 | 224 | .header { 225 | position: unset; 226 | border-bottom: none; 227 | margin: var(--space-3) 0; 228 | padding: var(--space-2) 0; 229 | background: unset; 230 | } 231 | 232 | .header .header__title { 233 | margin: var(--space-3) 0 var(--space-2); 234 | font-size: var(--font-size-5); 235 | } 236 | 237 | .footer { 238 | margin: var(--space-3) 0; 239 | } 240 | } 241 | 242 | /* Welcome component */ 243 | 244 | .welcome__container { 245 | display: flex; 246 | flex-direction: column; 247 | align-items: center; 248 | padding: var(--space-2) var(--space-2); 249 | } 250 | 251 | .logos { 252 | display: none; 253 | } 254 | 255 | .steps { 256 | flex-direction: column; 257 | width: 100%; 258 | } 259 | 260 | .steps .steps__list { 261 | list-style-type: none; 262 | padding: 0; 263 | } 264 | 265 | .steps .steps__entry { 266 | margin-bottom: var(--space-4); 267 | } 268 | 269 | .steps .steps__title { 270 | font-size: var(--font-size-5); 271 | line-height: var(--line-height-2); 272 | } 273 | 274 | .steps .steps__subtitle { 275 | font-size: var(--font-size-3); 276 | line-height: var(--line-height-2); 277 | } 278 | 279 | .steps .steps__text { 280 | font-family: var(--font-family-serif); 281 | line-height: var(--line-height-2); 282 | } 283 | 284 | .steps .steps__text a { 285 | color: var(--blue-600); 286 | text-decoration: none; 287 | } 288 | 289 | @media (min-width: 575px) { 290 | .welcome__container { 291 | width: 100%; 292 | } 293 | 294 | .logos { 295 | display: flex; 296 | align-items: center; 297 | margin: var(--space-6) 0 var(--space-5) 0; 298 | } 299 | 300 | .logos .logos__blur { 301 | display: flex; 302 | position: absolute; 303 | width: 375px; 304 | height: 115px; 305 | background: var(--magenta-100); 306 | filter: blur(82px); 307 | transform: rotate(-19deg); 308 | z-index: -1; 309 | } 310 | 311 | .logos .logos__plus { 312 | display: flex; 313 | font-family: var(--font-family-sans); 314 | font-weight: 800; 315 | font-size: var(--font-size-7); 316 | line-height: var(--line-height-6); 317 | margin: 0 var(--space-4); 318 | } 319 | 320 | .logos .logos__entry { 321 | display: flex; 322 | } 323 | 324 | .steps { 325 | max-width: var(--max-width-0); 326 | display: flex; 327 | padding: 0; 328 | } 329 | 330 | .steps .steps__subtitle { 331 | margin-top: var(--space-4); 332 | } 333 | 334 | .steps .steps__list { 335 | margin-top: 0; 336 | } 337 | } 338 | 339 | /* Card */ 340 | 341 | .card { 342 | display: flex; 343 | flex-direction: column; 344 | padding: var(--space-2); 345 | padding: 9px; 346 | position: relative; 347 | border-bottom: 1px solid #ced2d9; 348 | } 349 | 350 | .card .card__container { 351 | margin: 0 var(--space-1) 0; 352 | } 353 | 354 | .card .card__cover { 355 | width: 100%; 356 | height: 231px; 357 | -o-object-fit: cover; 358 | object-fit: cover; 359 | } 360 | 361 | .card .card__cover--none { 362 | width: 100%; 363 | height: 231px; 364 | background: var(--black); 365 | } 366 | 367 | .card .card__title { 368 | font-family: var(--font-family-sans); 369 | font-weight: 800; 370 | font-size: var(--font-size-7); 371 | line-height: var(--line-height-6); 372 | letter-spacing: -0.025em; 373 | margin: var(--space-3) 0; 374 | } 375 | 376 | .card .card__excerpt { 377 | font-family: var(--font-family-serif); 378 | font-weight: 400; 379 | font-size: var(--font-size-4); 380 | line-height: var(--line-height-3); 381 | margin-top: 0; 382 | } 383 | 384 | .card .card__date { 385 | font-weight: 600; 386 | font-family: var(--font-family-sans); 387 | font-size: var(--font-size-1); 388 | margin-top: calc(var(----space-4) + 7); 389 | } 390 | 391 | .card .card__link { 392 | color: var(--black); 393 | text-decoration: none; 394 | } 395 | 396 | .card .card__link:hover { 397 | opacity: 0.8; 398 | transition: 0.2s; 399 | } 400 | 401 | .card .card__link::before { 402 | content: ''; 403 | position: absolute; 404 | inset: 0; 405 | } 406 | 407 | .card:first-child { 408 | border-top-left-radius: 3px; 409 | border-top-right-radius: 3px; 410 | } 411 | 412 | .card:last-child { 413 | border-bottom-left-radius: 3px; 414 | border-bottom-right-radius: 3px; 415 | } 416 | 417 | @media (min-width: 575px) { 418 | .card { 419 | border: 1px solid #ced2d9; 420 | border-bottom: none; 421 | } 422 | 423 | .card .card__title { 424 | margin-top: var(--space-4); 425 | } 426 | 427 | .card:last-child { 428 | border-bottom: 1px solid #ced2d9; 429 | } 430 | } 431 | 432 | @media (min-width: 800px) { 433 | .card { 434 | flex-direction: row; 435 | } 436 | 437 | .card .card__container { 438 | margin: 0 var(--space-4) 0; 439 | } 440 | 441 | .card .card__cover, 442 | .card .card__cover--none { 443 | min-width: 366.5px; 444 | max-width: 366.5px; 445 | max-height: 231px; 446 | } 447 | } 448 | -------------------------------------------------------------------------------- /src/summarizer-app/articles/dummy_transcript.txt: -------------------------------------------------------------------------------- 1 | Oenator M. Deacon: Looking at child care and the proposal to set up a federal secretariat, as with all cross-jurisdictional issues, there will likely be some stress or tension on the file between the feds and the provinces. What role does your department envision for this proposed federal secretariat? 2 | 3 | Ms. Hall: Thank you for the question. 4 | 5 | The federal secretariat is intended to enhance the capacity of the federal government to be working with provinces and territories in support of the announcement in Budget 2021. It will enhance the internal capacity, in line with the increase in investment for early learning and child care. As well, it’s intended that the secretariat will support the ministerial table. At the moment, there is an ad hoc ministerial table, for example, and the secretariat will play a role in supporting that table and associated official committees. 6 | 7 | I would say provinces and territories also have their own administrative structures in their provincial bureaucracies. They are permitted to use a portion of the federal funding in support of their own administrative costs. They can use up to 10% of the funding that the federal government provides in support of their own administrative costs. It is intended that that would continue under the Canada-wide system. 8 | 9 | Senator M. Deacon: Thank you. I will leave that for a moment and come back to CMHC to ask a question that I think is built on about seven questions I’ve heard so far this afternoon connected to the Rapid Housing Initiative. 10 | 11 | I want to come back to the idea that there is a clear need in cities for high-density housing construction, but in some areas, municipal councils and developers continue to favour suburban sprawl. While the government has committed a good deal of money for this, what can we do at the federal level to see that the higher-density urban housing that is needed, as we see it, is what is being built? 12 | 13 | Ms. Bowers: Thank you for that question. We at CMHC are a big proponent of high-density housing in urban centres. We feel that is good in terms of affordability and also in terms of climate change and green initiatives. 14 | 15 | When you look at the programs in the National Housing Strategy, most of the programs are supply-based programs that support the creation of rental housing in our large cities. 16 | 17 | With respect to market-based rental housing, we observed several years ago that there had not been sufficient growth of purpose-built rental housing in Canada, and an initiative we called the Rental Construction Financing Initiative has been very successful. It started out as a $2 billion program, and it has grown to in excess of $23 billion in terms of government financing. We’ve had great interest and uptake of this program from commercial developers and non-profits as well. 18 | 19 | We feel, as the senator mentioned, NIMBYism, the development process and zoning issues in the cities are often significant barriers to creating greater density. Even though these are all municipal and provincial issues, we feel that CMHC has a role in working with various partners to identify the barriers to housing supply creation and to be a constructive partner in unblocking these barriers. 20 | 21 | Senator M. Deacon: Thank you. 22 | 23 | [Translation] 24 | 25 | Senator Moncion: My first question goes to Ms. Bowers, from the CMHC. It is about the securitization program. Could you tell us what that amount is at the moment? 26 | 27 | [English] 28 | 29 | Ms. Bowers: I’m sorry, Mr. Chair, I did not catch the name of the program. If I could ask the senator to repeat the question, I would appreciate that. 30 | 31 | [Translation] 32 | 33 | Senator Moncion: The securitization program. 34 | 35 | [English] 36 | 37 | Ms. Bowers: With respect to that question regarding securitization, I would like to refer the question to CFO, Lisa Williams. 38 | 39 | Ms. Williams: At the end of the first quarter of 2021, our outstanding guarantee for securitization is $479 million. 40 | 41 | Senator Moncion: Thank you for that answer. How much of the securitization program that you have contributes or doesn’t contribute to the high volume of transactions on the market right now? 42 | 43 | [Translation] 44 | 45 | We know that the interest rates provided by CMHC are much lower than the conventional rates provided by financial institutions. It’s a kind of stress test, trying to make loans to buyers at a certain rate of interest so that they become part of CMHC’s mortgage lending and eventually end up on the mortgage market. 46 | 47 | To what extent is that contributing to the growth in the mortgage market in Canada at the moment? 48 | 49 | [English] 50 | 51 | Ms. Bowers: Thank you very much for that question, senator. With respect to our securitization programs, the purpose of the program is to provide stable, low-cost funding to financial institutions so that regardless of where you live in Canada, you have access to housing finance. Having said that, we are very conscious of the fact that if the pricing that we place on securitization is too low, it can actually contribute to people being able to access mortgages too readily. That is a risk we’re conscious of. 52 | 53 | When you look at the history of pricing for our securitization product over the last five years, you will see that every year we have increased the pricing. We want to create some availability to the programs, but we are conscious that there has to be limits in terms of controlling the amount that is available to financial institutions, because we’re aware of the risks that exist from an economic perspective. That’s something we balance, the need to provide access to financing but at a cost that is reasonable and doesn’t create increased risk for the government and the economy as a whole. 54 | 55 | Senator Moncion: Thank you. Could you tell us the maximum amount that you will be allowed to work with under this program? 56 | 57 | Ms. Bowers: Perhaps I could ask our CFO to answer that question. We set annual limits on that, and she may have access to those numbers. 58 | 59 | Ms. Williams: Yes. I believe the limit is $750 billion for securitization in the current years, and that limit was increased as a result of the pandemic. I’m searching for the date, but in a couple of years’ time, we will come back to our original limit of $600 billion. 60 | 61 | Senator Moncion: I will ask the question again, then. 62 | 63 | [Translation] 64 | 65 | My next question is about early childhood education and daycare places. 66 | 67 | We have heard testimony from Ms. Hall, who appeared before the social affairs committee and answered our questions extremely well. She is really familiar with her area. 68 | 69 | In terms of the eligibility criteria for daycare places, we know that, in Quebec, not everyone has access to daycare at $7. How will those daycare places be assigned? After all, the program was created to assist women living in poverty or with no possibility of returning to the workforce. Could you tell me about the eligibility criteria and the program’s standards for ensuring that the groups in most need are served first, not the families that have the means to pay for daycare costs. 70 | 71 | Ms. Hall: Thank you for the question. 72 | 73 | [English] 74 | 75 | That is a big question, and thank you for it. 76 | 77 | I would say a couple of things. First, we are certainly aware of the lessons from Quebec and that it is important to be mindful of those who benefit from the program and the quality of the program. 78 | 79 | So moving forward, we will be in negotiations with provinces and territories. In fact [Technical difficulties] at this point in time. 80 | 81 | So we will be working very soon with the provinces and territories to ensure that the programs that they create with the federal funding do provide meaningful and wide access to the programs, including for families living in low income, Black and racialized families, Indigenous families, official language minority communities and others to ensure access is wide and there is a broad benefit from the programs that are available for families and for children. 82 | 83 | Senator Moncion: If I understand correctly, you don’t have any criteria or targets that are identified for women or for low-income families or even targets for families who can afford child care? 84 | 85 | Ms. Hall: I would say the government did articulate some goals in Budget 2021 that include that child care would be available for all families that need it. That really does speak to very broad access and availability. 86 | 87 | The budget also recognized the impact of child care for employment for women or for secondary earners in families and laid out the economic impact that the investment in child care is expected to make. The budget cited a range of studies that suggested the impact of every dollar invested in child care ranges from about $1.50 to $2.80 in terms of the broader economic impact. It also outlined a projection and estimate of GDP growth of 1.2% resulting from this investment. In large part, that’s due to increased revenues and economic activities stemming from women either joining the labour market or being able to work more at jobs they have. 88 | 89 | Senator Moncion: Thank you very much. 90 | 91 | The Chair: Honourable senators, looking at the clock, we have approximately 10 minutes left for the second round. If officials cannot answer in the time provided, we will ask you to send the answers to us in writing, please, on or before June 11. 92 | 93 | Senator Marshall: My question is for CMHC with regard to the Canada Emergency Commercial Rent Assistance Program. I thought that program was finished and it had moved over to the Canada Revenue Agency. I was surprised when I saw the $15.8 million. Can you just explain that? Is this a winding down of the program? 94 | 95 | Ms. Bowers: The senator is absolutely correct. It is for the winding down of the program. Q&A activities are conducted to ensure proper governance in the use of funds, and the funds requested are to pay for those activities. 96 | 97 | Senator Marshall: Would that be for businesses that didn’t receive their subsidy? 98 | 99 | Ms. Bowers: No. They are costs that would be required by our administrator to make sure that the checks and balances and other processes we have in place to ensure that the program has been run properly and that people have received the funds that were allocated and all those governance processes had been completed in the appropriate manner. 100 | 101 | Senator Marshall: It is like a post-review. Thank you. 102 | 103 | [Translation] 104 | 105 | Senator Forest: Thank you very much. My question goes to Ms. Robertson, to whom I also send sincere congratulations. 106 | 107 | You are allocating an envelope of $5 million over two years for a huge consultation. Can you explain to us why that consultation will take two years? A lot of consultation has already been done on the matter. Will it just be about self-employed workers? Or will there be much broader consultation on the entire employment insurance program, especially in regions like ours, where seasonal industries like agriculture, fishery and forestry are very important for our residents? 108 | 109 | [English] 110 | 111 | Ms. Robertson: Thank you so much for the question. Mr. Chair, I would like to redirect again to my colleague Elisha Ram. 112 | 113 | The Chair: Mr. Ram, you can send it in writing or answer this in the next 40 seconds, please. 114 | 115 | Mr. Ram: Thank you for the question, honourable senator. I can tell you that the consultations are going to be broad. They’re going to go considerably beyond just the issue of self-employed and will include issues like seasonal work, access and adequacy and other issues that touch the EI program. Because of the complexity and the range of issues that we would like to consult, it will explain why it will be a multi-stage process that will take up to two years. 116 | 117 | Senator Klyne: This question is for ESDC. With regard to Budget 2021, the government is looking to connect up to 90,000 Canadians with training they need to access good jobs in sectors where employers are looking for skilled workers. In Supplementary Estimates (A), ESDC is spending $332 million for a comprehensive training strategy to drive recovery. I wonder if those two are connected. Regardless of whether they are or not, is the $332 million — has the government considered the disproportional impact the pandemic has had on employment prospects for women and visible minorities? Are any of these funds focused on Indigenous communities, such as those in the tourism industry, to gain greater access to other skilled labour markets? 118 | 119 | Ms. Robertson: Thank you, Senator Klyne. I will go to Rachel Wernick who is running this important program. I’m not sure if she can answer in 40 seconds. That was a very loaded question. 120 | 121 | Ms. Wernick: I think there is a reference to several programs there in terms of the question. 122 | 123 | The Chair: Can you respond in writing? 124 | 125 | Ms. Wernick: I think that would do better justice to the senator’s question. Thank you. 126 | 127 | The Chair: That would give us due diligence. Thank you. 128 | 129 | Senator Loffreda: I’d like to refocus again on homelessness, which is becoming more and more of a concern in this pandemic. My question is for Employment and Social Development Canada. 130 | 131 | In your departmental plan, you will be requiring communities to publicly report on progress made in preventing and reducing homelessness, beginning in fiscal year 2021-22. Can you speak to us about this reporting obligation? How do you define “community?” In other words, who has the reporting authority? I imagine communities already have proactively reported on homelessness, but do you expect some communities to have reporting challenges? I always say what you measure improves. What is the trend? Do you have any historical records? It’s a welcome requirement. Thank you. 132 | 133 | Ms. Robertson: Would you like us to reply in writing, Mr. Chair, or would you like my colleague Janet Goulding to try to answer in a very short period of time? 134 | 135 | Senator Loffreda: In writing is fine. If you have one sentence quickly. Is this new? Are there any trends? And the rest you can do in writing. 136 | 137 | Ms. Goulding: I can say that the communities that report are the 64 designated communities that are part of the program. So we can submit that in writing to the committee. The first committee reports are expected later this summer. 138 | 139 | [Translation] 140 | 141 | Senator Dagenais: I have a very quick question. I would be fine with a written answer. What are the obstacles to the rapid housing initiative? Is it cities making land available? Is it provincial government regulations? Or is it that some dissenting groups don’t want affordable housing in their neighbourhoods? 142 | 143 | Could there be a lack of resources in your organization? I would be very grateful if you can send me the answer in writing. 144 | 145 | The Chair: Thank you very much, Senator Dagenais. 146 | 147 | [English] 148 | 149 | Honourable senators, as we conclude this meeting, to the witnesses, thank you very much. You have been very informative and professional. To Ms. Bowers and Ms. Robertson, we have certainly seen great leadership from both of you in this committee. There’s no doubt in my mind that we will be asking you to come back. 150 | 151 | That said, honourable senators, our next meeting is on Tuesday, June 15, at 9:30 a.m. EST. I declare the meeting adjourned. 152 | 153 | (The committee adjourned.) -------------------------------------------------------------------------------- /src/summarizer-app/articles/2.txt: -------------------------------------------------------------------------------- 1 | e're counting—a minimum of three standards here. One is our current standard that “a miscarriage of justice 'likely occurred.'” The other is the U.K. standard that there's a “real possibility” that a miscarriage of justice occurred, and then there's the new standard in Bill C-40. The new standard in Bill C-40 is “that a miscarriage of justice may have occurred”. 2 | That's why, Madam Chair, I have real concerns about reconciling NDP-1 and LIB-1 and explaining how this wouldn't open up an absolute tsunami of applications. This is a very subjective test, and depending on how the commission chooses to operate, we could have a ridiculous volume of frivolous cases with that standard. 3 | (1730) 4 | 5 | I'm not suggesting, necessarily, that the current standard is the appropriate one. The current standard is that it “likely occurred”, which I take to mean that the minister feels there's at least a 51% chance that there was a miscarriage of justice. To me, the U.K. standard is more reasonable. That's why later on, once we've dealt with NDP-1 and LIB-1—I'm not speaking to it now, but later on—you'll hear us move a Conservative amendment that would change that standard from “may have” occurred to the U.K. standard of “likely” occurred. I think that's completely reasonable. I think that will protect this commission and protect Canadians' perception of our justice system. 6 | I was looking at some polling. I'd encourage all members to look at the polling on how Canadians feel about our justice system. It's pretty dismal. Canadians are really concerned about our system of justice in Canada. A top concern is that the rights of victims are protected and that the individuals who should be behind bars are in fact behind bars. We have to be very careful. In Bill C-40 we have to get it right. At the outset, when I speak to NDP-1, it ties in directly to this standard that a miscarriage of justice “may” have occurred. 7 | Following on the idea of the CCRC, the U.K. commission, the idea of a Canadian CCRC obviously has significant support among experts and stakeholders. Some people argue that it's potentially too costly. Canada has a low number of identified wrongful convictions. You could take that to mean a couple of different things. You could say that we're not finding enough wrongful convictions; you could also say that our system of justice is effective at preventing wrongful convictions. I mentioned some of the safeguards we have in place. 8 | I think it was the individual whom Mr. Caputo had recommended as a witness—a former associate of his who spoke very highly of Mr. Caputo—who brought to the attention of the committee some very interesting testimony. 9 | What was his name? 10 | An hon. member: Was it Mr. Wiberg? 11 | Hon. Rob Moore: Yes. He had some very interesting testimony. 12 | He was reminding committee members that the landscape around our system of justice has changed remarkably since some of the more high-profile cases around wrongful convictions in this country. There's been the coming into effect of the charter, of legal aid and of DNA evidence. DNA evidence didn't exist at the time of some of these wrongful convictions. DNA evidence can be used to convict and DNA evidence can be used to exonerate. 13 | I need to speak about the North Carolina experience. I wouldn't want anyone to be under any illusion that what's being proposed here is in any way in sync with what North Carolina has done once we've heard that testimony. 14 | North Carolina requires evidence of factual innocence. I asked the witness from North Carolina why they came up with that standard. She said it was the standard that they found would be acceptable to the people in North Carolina. From talking with my constituents about the justice system, which I do, and hearing from other members of Parliament from all parties on what they hear from their constituents, I have to believe that Canadians' expectations around wrongful conviction more closely mirror what North Carolina has proposed versus what is being proposed in Bill C-40, should it be broadened—that is, if there is new evidence to suggest that it is likely that someone who was convicted of an offence was innocent, every single one of us should want that person to be completely exonerated if that person is found, through DNA evidence or other evidence, to have been wrongfully convicted of a crime they didn't commit. 15 | (1735) 16 | 17 | You will remember the case of O.J. Simpson. He immediately said that he would go out and look for the person who actually committed the crime. Well, most people thought they had the person who had committed the crime the first time. That's the kind of response there should be when there is a wrongful conviction found within our system. It should be that strong; Canadians should say, “We need to find the person who really did this.” That is not the standard in Bill C-40. 18 | Why do I mention that? Bill C-40 is tenuous enough, with the.... I would say we need to have a robust system, obviously, for individuals who have been wrongfully convicted. We have a system now. The Minister of Justice is ultimately responsible for that system. We have a threshold now that says, “a miscarriage of justice likely occurred”. We could debate around this table whether that is too high a threshold, but I can tell members that if we were to poll our constituents and ask what the standard should be, they would be much more likely to say the bar should be “when there's a real possibility there was a miscarriage of justice” rather than a convicted individual who doesn't appeal their sentence being able to avail themselves of the commission. What standard does the commission apply? Well, there “may” have been a miscarriage of justice. Is it based on new evidence? Not necessarily; it's based on the whims of the commission at that time. This is where we're heading should Bill C-40 be amended and broadened in its scope. 19 | I'm not going to put anyone on the spot. I'll answer my own question. When the minister and the cabinet considered Bill C-40 before it was tabled, and on the advice they would have received from departmental officials.... There is a reason an individual, except under exceptional circumstances, has to appeal the decision. There's a reason inherent in that. There's a reason that this standard is meshed with that requirement. The ultralow standard that a miscarriage of justice “may” have occurred requires the step of having to appeal. To introduce the possibility of not appealing at all calls the low threshold into question even further,. 20 | It's for those reasons I have concerns about NDP-1. 21 | We did a study recently, as a committee, on the federal government's obligations to victims of crime. I think of that study often when I look at other pieces of legislation. That's a lens—I hope we all agree—we should somewhat look through. That's a lens that should always be on our mind when we look at any piece of legislation. Right now, I'm looking at Bill C-40, and specifically NDP-1. I want to look at Bill C-40 and amendment NDP-1 through the lens, at least, of victims of crime. When someone feels they were wrongfully convicted—even though, under this provision, they may have committed the offence—what does a victim of crime say about a process that's going to involve dredging up their concerns and revictimizing them? I don't throw that out lightly. The process revictimizes victims. That's why we need to get this right. 22 | (1740) 23 | 24 | We heard that testimony at this committee. We heard that from victims who have lost loved ones. They have said that having to go to parole hearings, having to know that their daughter has to go to a parole hearing, that when they pass on, their daughter will go to a parole hearing of the individual who murdered their husband.... We heard the testimony that it revictimizes victims. Victims have been through enough, so when we create a system that could amount to a reopening of these very hurtful cases for victims, we'd better be sure that we're dealing with cases that we ought to be dealing with. 25 | That is why.... We have a system of justice. I think it was Mr. Van Popta who rightly mentioned that some of the fixes that people are trying to incorporate into this catch-all may be better placed in other areas—for example, access to justice, legal aid. The question was put to Minister Virani about making sure that vacancies in the system of judges are filled, making sure that people can get a hearing, making sure that there's timely access to justice—there's the old expression of “justice delayed is justice denied”—and all those things. 26 | This commission cannot be a fix-all for everything that's wrong in the justice system; this commission should be about the wrongfully convicted. With NDP-1, I fear that we are steering away from that principle and into an area that I don't think Canadians would be supportive of: the possibility of opening up a parallel justice system, another avenue to avail yourself of when you've been convicted of a crime. You may choose, “Well, I'm not going to appeal my sentence as I'm supposed to do. I'm convicted. I'm not going to appeal. I'm going to try out this new commission.” What's the standard for that commission? I know that within the criminal system, the standard is “beyond a reasonable doubt”. Within this system, the standard is that “a miscarriage...may have occurred”. 27 | I was speaking a bit about victims. I look at the U.K. treatment of victims, and what “The Wrongful Convictions in Canada” paper says—and I think this is instructive for us—is that: 28 | the CCRC has been criticized for not having objective standards to determine the scope of investigations, with neither a minimum amount of investigation required, nor a logical end point to the open-ended task or proving the absence of error. 29 | The U.K. has its challenges, too, even with its higher standard, but it was clear from the testimony that the U.K. takes that investigative responsibility serious. When it comes to victims, the CCRC says: 30 | The CCRC will not contact a victim just because we are a looking at a case. 31 | Now listen to what they say next: 32 | This is because most of the cases we look at are not sent for an appeal. 33 | Why? It's because the standard is that “there is 'a real possibility' that the conviction would not be upheld.” Their standard is not that the conviction may not be upheld; it's that “there is 'a real possibility'”. 34 | The CCRC goes on: 35 | We know that victims and their families have already had stressful experiences. Finding out their case is under review can make them feel they are having to relive it all again [and] are not believed. We do our best to avoid causing unnecessary distress where we can. 36 | That's the U.K. It's saying that it's not going to put victims through a frivolous.... It's acknowledging that it's not going to hear a lot of the cases, the applications, that come to it because it has a standard. It's saying that it's not going to put victims of crime through this just because someone says, “I didn't get a fair shake. I was wrongfully convicted. I'm going to take a shot at the CCRC.” It's saying that it doesn't even notify the victim right away because it doesn't want to stress out the victim and the victim's family. It knows what this will put them through—to hear that the person who was convicted of maybe murdering a friend or a family member is now going to suggest that they were wrongfully convicted. 37 | The CCRC says: 38 | If the CCRC decides to send a case for an appeal, we will always try our best to tell the victim or their family. 39 | It also says: 40 | (1745) 41 | 42 | If a victim or their family feels we have not acted in accordance with our policy they can complain, using our complaints procedure. Our Customer Service Manager will take an independent look at the issue raised. 43 | I haven't heard from the NDP or the Liberals on how they reconcile. If I had seen a two-part amendment, if I had seen an amendment that said we don't want to require appeals but we do agree with having a higher standard, I might want to take a closer look at the amendment, although there's a reason that the justice minister had the requirement of an appeal. 44 | For those reasons, I would urge extreme caution around both NDP-1 and LIB-1. They do not mesh with the full context of the bill, which has an extremely low access point of “a miscarriage of justice may have occurred”. 45 | I think I'll wrap up my remarks for now on NDP-1. I have some questions that I am going to put to our witnesses who are here. I might save that for a bit. 46 | I just wanted to make some comments early on to everyone and to our committee members about the U.K. experience as we deliberate on NDP-1, because what I mentioned was not part of the testimony that we heard; it is through some deeper digging that I had done on the U.K. experience. I find their concern around victims, their rationale behind their higher threshold, and the fact that even with their higher threshold, they were met with an enormous volume of applicants to be incredibly compelling and instructive. 47 | We have to be prepared for that too. We are going to have an enormous volume of applicants. Unless we want to completely ignore the entire U.K. experience—and they have years of experience on this—and unless we want to completely ignore their rationale and their lived experience in having a commission, we are not only going to face an enormous volume of applicants, but we are also going to cause enormous disruption to victims and their families if we don't get Bill C-40 right. If the threshold is too low, this is going to cause enormous hurt to families of individuals who were killed or injured by those who have been through our justice system and have been convicted, having not even appealed that conviction. 48 | I will conclude my remarks on that note on NDP-1 for now. 49 | (1750) 50 | 51 | [Expand] 52 | The Chair: 53 | Go ahead, Mr. Caputo. 54 | [Expand] 55 | Mr. Frank Caputo: 56 | Thank you, Madam Chair. 57 | I'm going to go through NDP-1 here a bit now. What I see here is that at the outset, it speaks about replacing line 35 on page 2 with the following.... I do this because I sometimes have difficulty placing it unless I read the whole thing together. 58 | It would read: 59 | For the purposes of subsection 696.4(3), the application must include information indicating whether the person’s rights to appeal the finding or verdict have been exhausted and, if they have not been exhausted, information relevant to any factors that the applicant believes should be taken into account for the purposes of subsection— 60 | As it currently reads, it's: 61 | ....relevant to the factors referred to in subsection 696.4(4). 62 | Those subsections are under proposed subsection 696.4(4), and I'm looking at page 3 right now. It reads: 63 | the amount of time that has passed since the final judgment of the court of appeal; 64 | If I understand the NDP amendment, it would remove the requirement to consider these things, but the amount of time that has passed since the judgment until the time of appeal is quite relevant. 65 | Next, it reads: 66 | the reasons why the finding or verdict was not appealed to the Supreme Court of Canada; 67 | Now, we have heard a fair amount of evidence. We've also heard a number of submissions from members of this committee as to why or why not something may have been appealed to the Supreme Court of Canada, and why or why not something may have been appealed to a provincial court of appeal. Again, I think that it's probably a relevant factor as to why we should be considering whether or not the commission should review the application. 68 | Next, it reads: 69 | whether it would serve a useful purpose for an application to be made for an extension of the period within which a notice of appeal or a notice of application for leave to appeal, as the case may be, to the Supreme Court of Canada may be served and filed; 70 | This is an interesting point here in proposed paragraph 4(c). The reason I say that is we have talked about legal aid and its lack of funding. Ultimately, the final say on whether a decision is appealed rests with the accused, who is obviously going to be the client. A lawyer can't put forward an appeal if their client does not wish to put forward the appeal. 71 | It's obviously quite relevant. This is interesting to me, because what this is asking is whether it would serve a useful purpose for an application to be made for an extension of a period of time for an appeal when this person is claiming that a miscarriage of justice has occurred. I wonder whether provincial legal aid bodies would look at this provision, take heed of it and say that when they're dealing with something that is a potential miscarriage of justice or an allegation of a miscarriage of justice, they will appoint counsel in order to seek an extension of time to appeal. The mischief that NDP-1 and LIB-1 are addressing, as I understand it, is that some people wouldn't have appealed because they didn't have the means. If I understand it correctly, that's one of the issues. 72 | What if proposed paragraph 4(c) is really an exhortation to say to legal aid in the provinces, “Look, you should be funding these appeals. In that case, seek leave to extend the time to appeal and then seek to appeal”? In that case, we are actually looking at a court of appeal maybe saying that on the basis of the appeal, there is no need to go before the commission. 73 | (1755) 74 | 75 | We have to remember that the court of appeal is the mainstream process. Everybody who's in the room knows that when somebody is unhappy with their decision, they have the right of appeal. That is the main way that things are done. 76 | This is actually quite a revolutionary piece of legislation, when we think about it, because we're establishing a commission. We'll have commissioners appointed by government who don't necessarily, as I recall, need to have a legal background. This is a parallel set of proceedings. 77 | The question of whether a person should have to have exhausted their appeals and whether that should be material, I think, is quite a live issue, especially if the provinces—reflecting on what we've had to say and listening to what Mr. Housefather and Mr. Garrison have had to say about people maybe not having had the ability—ask if the recourse shouldn't first be to the court of appeal. That is the whole point of our system. When you have been aggrieved, when the court below gets it wrong, you go upstairs. That's what we would say, right? You would say that it's time to go upstairs to the court of appeal. 78 | I find that interesting and I wonder whether we should be circumventing the necessity for an appeal. I wonder—I'm just thinking out loud here—whether an amendment could actually be made, and whether there might be.... I'm just trying to think about the cases that relate to the funding of appeals, especially if there's a bona fide potential for a miscarriage. If somebody shows that they have a bona fide case that there “may” have been a miscarriage of justice—not even at a high threshold, because we're not talking about overturning the appeal but only about the appointment of counsel to simply help somebody to appeal—then in that case, I don't think anybody around this table would say, “You suffered what could have been a miscarriage of justice 30 years ago. You did not have the means to appeal it. Therefore, this legislation will not only establish the commission to do it, but it will also establish a mechanism by which you could pursue that in a more streamlined manner.” 79 | I'm not sure. I'm not going to ask the experts to comment on it because I know that's not an easy thing to do, given that my thoughts on this are still coming together, but— 80 | (1800) 81 | 82 | [Expand] 83 | Mr. Randall Garrison: 84 | I have a point of order, Madam Chair. 85 | In light of some of the comments that Mr. Caputo has made, I believe there may be an error in drafting my amendment. I would like to withdraw my amendment in favour of LIB-1. 86 | [Expand] 87 | The Chair: 88 | Okay. Thank you. 89 | An hon. member: That's amazing. 90 | The Chair: Okay. I take it that everybody agrees to the withdrawal of the amendment. I guess that's what I'm hearing. 91 | [Expand] 92 | Hon. Rob Moore: 93 | I have a point of order, Madam Chair. 94 | [Expand] 95 | The Chair: 96 | Go ahead. 97 | [Expand] 98 | Hon. Rob Moore: 99 | Just for clarification, if Mr. Garrison, once the amendment is moved, it becomes the committee's, I think. We would have to agree to then withdraw it. 100 | [Expand] 101 | The Chair: 102 | Yes. That's what I asked. 103 | [Expand] 104 | Mr. Frank Caputo: 105 | Can we have a chat for a couple of minutes? Is that possible? 106 | [Expand] 107 | The Chair: 108 | Well, I can give you a couple of minutes. I'm thinking you're very happy with that. 109 | [Expand] 110 | Mr. Frank Caputo: 111 | It's not happiness— 112 | [Expand] 113 | The Chair: 114 | Certainly. Take a couple of minutes. I'll suspend for two minutes. Go ahead. 115 | (1800) 116 | 117 | (1805) 118 | 119 | [Expand] 120 | The Chair: 121 | All right. We're back. I'm calling the meeting back to order, please. 122 | I'm calling a vote on the withdrawal of the.... 123 | [Expand] 124 | Hon. Rob Moore: 125 | Madam Chair, I have a point of order. Normally we meet from 3:30 to 5:30. In light of the time, in light of the fact that it's taking a long time to get through these amendments, and I suspect it will continue, I make a motion that we adjourn the meeting at this point. 126 | [Expand] 127 | The Chair: 128 | Do you want a recorded vote? 129 | [Expand] 130 | Hon. Rob Moore: 131 | Yes. 132 | [Expand] 133 | The Chair: 134 | Mr. Clerk, please go ahead— 135 | A voice: [Inaudible—Editor] first. Mr. Garrison asked— 136 | The Chair: Oh, I can't do that. I'm sorry. I have to deal with the withdrawal first. 137 | Is there any objection to the withdrawal? 138 | There is no objection. We have unanimous consent to withdraw that. 139 | (Amendment withdrawn) 140 | The Chair: Thank you very much. 141 | [Expand] 142 | Hon. Rob Moore: 143 | Madam Chair, there was much made about how long the meeting was scheduled for and that our witnesses are here— 144 | [Expand] 145 | The Chair: 146 | Now I'll deal with a different point— 147 | [Expand] 148 | Hon. Rob Moore: 149 | That's why I'm moving a motion. 150 | Normally we meet from 3:30 to 5:30. We're heading into Christmas season. At the rate we're going, we're not going to pass Bill C-40 tonight, no matter what. There are just too many clauses to go through. 151 | We're scheduled to meet in the new year on January 29 or 30. I would suggest that we adjourn now and everyone have a merry Christmas. Then we'll get back to work when the House reconvenes. 152 | I move that we adjourn. 153 | [Expand] 154 | The Chair: 155 | We have a motion on the floor to adjourn. 156 | We'll have a recorded vote—I hear you, Mr. Maloney—and I will ask the clerk to please take over. 157 | [Expand] 158 | Mrs. Rachael Thomas: 159 | Clerk, I know if you seek it you will see that he is actually officially subbed in and I am no longer. 160 | The Clerk: I'm on the mailbox here. I didn't receive anything. 161 | [Expand] 162 | The Chair: 163 | Ms. Thomas, you are here. Do you mind getting this over with? 164 | We'll go back to Mr. Mendicino. 165 | (1810) 166 | 167 | [Expand] 168 | Hon. Marco Mendicino (Eglinton—Lawrence, Lib.): 169 | Yes. 170 | [Expand] 171 | The Chair: 172 | I know you're acknowledging that I'm speaking with you. 173 | [Expand] 174 | Mr. Garnett Genuis (Sherwood Park—Fort Saskatchewan, CPC): 175 | No, this is B.S. You can't do this. This is an attack on the rights of members of Parliament. He cast his vote. You're not going to get away with this. 176 | [Expand] 177 | Mr. James Maloney: 178 | Madam Chair, follow the procedure. 179 | [Expand] 180 | Mr. Garnett Genuis: 181 | He cast his vote and you called the vote. We're not going to play these games. 182 | [Expand] 183 | Mr. James Maloney: 184 | You're the one playing the games. 185 | [Expand] 186 | Mr. Garnett Genuis: 187 | No. He voted. You can't just sit here in the middle of a vote because you don't like the result. 188 | [Expand] 189 | Mr. James Maloney: 190 | Ask him the question. Ask him if he voted. 191 | [Expand] 192 | Mr. Garnett Genuis: 193 | Call the vote, clerk. It's the clerk who calls the results of the vote. 194 | [Expand] 195 | Ms. Anju Dhillon: 196 | Does he have...? 197 | He doesn't have his thing either. 198 | [Expand] 199 | Hon. Marco Mendicino: 200 | To clarify, I was acknowledging, and I vote no. 201 | [Expand] 202 | Mr. Garnett Genuis: 203 | Oh, what is this? This is not allowed. You can't just sit here and change the vote until you get what you want. 204 | This is not going to fly. This is not going to fly at all. You need to respect the rules of the committee and adjourn. 205 | [Expand] 206 | The Chair: 207 | Let's take a second, please. 208 | (1810) 209 | 210 | (1815) 211 | 212 | [Expand] 213 | The Chair: 214 | The vote stays as it is. It was a no, unless there is unanimous consent to change his vote. 215 | An hon. member: The vote was a yea. 216 | The Chair: I mean.... 217 | A hon. member: No, there's not unanimous consent. 218 | The Chair: Yes, okay. 219 | We will adjourn. --------------------------------------------------------------------------------