Add a democratic idea submission and AI synthesis platform
Implement a full-stack application with a React frontend and a Python Flask backend. The backend integrates with an AI agent to filter political ideas for democratic values and synthesize accepted ideas into a collective voice. Includes API endpoints for idea submission, retrieval, and synthesis, along with database persistence. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 923ae0e3-a363-4db8-b04a-e8baca2a1330 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 31c5f770-9905-46af-a938-9d40ef3d4404 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8af7d2ec-2cc3-4ece-8af3-9f071488d072/923ae0e3-a363-4db8-b04a-e8baca2a1330/Xzzm5QH Replit-Helium-Checkpoint-Created: true
This commit is contained in:
@@ -8,3 +8,53 @@
|
||||
export interface HealthStatus {
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface SubmitIdeaBody {
|
||||
/**
|
||||
* The political idea text
|
||||
* @minLength 10
|
||||
* @maxLength 1000
|
||||
*/
|
||||
content: string;
|
||||
/**
|
||||
* Optional pseudonym
|
||||
* @maxLength 100
|
||||
*/
|
||||
author?: string;
|
||||
}
|
||||
|
||||
export interface Idea {
|
||||
id: number;
|
||||
content: string;
|
||||
author?: string | null;
|
||||
accepted: boolean;
|
||||
rejectionReason?: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface IdeaResult {
|
||||
id: number;
|
||||
accepted: boolean;
|
||||
/** Reason if rejected */
|
||||
reason?: string;
|
||||
idea?: Idea;
|
||||
}
|
||||
|
||||
export interface IdeaStats {
|
||||
total: number;
|
||||
accepted: number;
|
||||
rejected: number;
|
||||
}
|
||||
|
||||
export interface Synthesis {
|
||||
/** The synthesized voice of the people */
|
||||
text: string;
|
||||
/** Number of ideas included in the synthesis */
|
||||
ideaCount: number;
|
||||
updatedAt?: string | null;
|
||||
}
|
||||
|
||||
export interface ErrorResponse {
|
||||
error: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
@@ -5,18 +5,29 @@
|
||||
* API specification
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import type {
|
||||
MutationFunction,
|
||||
QueryFunction,
|
||||
QueryKey,
|
||||
UseMutationOptions,
|
||||
UseMutationResult,
|
||||
UseQueryOptions,
|
||||
UseQueryResult,
|
||||
} from "@tanstack/react-query";
|
||||
|
||||
import type { HealthStatus } from "./api.schemas";
|
||||
import type {
|
||||
ErrorResponse,
|
||||
HealthStatus,
|
||||
Idea,
|
||||
IdeaResult,
|
||||
IdeaStats,
|
||||
SubmitIdeaBody,
|
||||
Synthesis,
|
||||
} from "./api.schemas";
|
||||
|
||||
import { customFetch } from "../custom-fetch";
|
||||
import type { ErrorType } from "../custom-fetch";
|
||||
import type { ErrorType, BodyType } from "../custom-fetch";
|
||||
|
||||
type AwaitedInput<T> = PromiseLike<T> | T;
|
||||
|
||||
@@ -99,3 +110,308 @@ export function useHealthCheck<
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit a civic idea to be filtered and included in the synthesis
|
||||
* @summary Submit a political idea
|
||||
*/
|
||||
export const getSubmitIdeaUrl = () => {
|
||||
return `/api/ideas`;
|
||||
};
|
||||
|
||||
export const submitIdea = async (
|
||||
submitIdeaBody: SubmitIdeaBody,
|
||||
options?: RequestInit,
|
||||
): Promise<IdeaResult> => {
|
||||
return customFetch<IdeaResult>(getSubmitIdeaUrl(), {
|
||||
...options,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", ...options?.headers },
|
||||
body: JSON.stringify(submitIdeaBody),
|
||||
});
|
||||
};
|
||||
|
||||
export const getSubmitIdeaMutationOptions = <
|
||||
TError = ErrorType<ErrorResponse>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof submitIdea>>,
|
||||
TError,
|
||||
{ data: BodyType<SubmitIdeaBody> },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof submitIdea>>,
|
||||
TError,
|
||||
{ data: BodyType<SubmitIdeaBody> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["submitIdea"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof submitIdea>>,
|
||||
{ data: BodyType<SubmitIdeaBody> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return submitIdea(data, requestOptions);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type SubmitIdeaMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof submitIdea>>
|
||||
>;
|
||||
export type SubmitIdeaMutationBody = BodyType<SubmitIdeaBody>;
|
||||
export type SubmitIdeaMutationError = ErrorType<ErrorResponse>;
|
||||
|
||||
/**
|
||||
* @summary Submit a political idea
|
||||
*/
|
||||
export const useSubmitIdea = <
|
||||
TError = ErrorType<ErrorResponse>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof submitIdea>>,
|
||||
TError,
|
||||
{ data: BodyType<SubmitIdeaBody> },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof submitIdea>>,
|
||||
TError,
|
||||
{ data: BodyType<SubmitIdeaBody> },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getSubmitIdeaMutationOptions(options));
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns all ideas that passed the democratic filter
|
||||
* @summary List all accepted ideas
|
||||
*/
|
||||
export const getListIdeasUrl = () => {
|
||||
return `/api/ideas`;
|
||||
};
|
||||
|
||||
export const listIdeas = async (options?: RequestInit): Promise<Idea[]> => {
|
||||
return customFetch<Idea[]>(getListIdeasUrl(), {
|
||||
...options,
|
||||
method: "GET",
|
||||
});
|
||||
};
|
||||
|
||||
export const getListIdeasQueryKey = () => {
|
||||
return [`/api/ideas`] as const;
|
||||
};
|
||||
|
||||
export const getListIdeasQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof listIdeas>>,
|
||||
TError = ErrorType<unknown>,
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<Awaited<ReturnType<typeof listIdeas>>, TError, TData>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}) => {
|
||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getListIdeasQueryKey();
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof listIdeas>>> = ({
|
||||
signal,
|
||||
}) => listIdeas({ signal, ...requestOptions });
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listIdeas>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type ListIdeasQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listIdeas>>
|
||||
>;
|
||||
export type ListIdeasQueryError = ErrorType<unknown>;
|
||||
|
||||
/**
|
||||
* @summary List all accepted ideas
|
||||
*/
|
||||
|
||||
export function useListIdeas<
|
||||
TData = Awaited<ReturnType<typeof listIdeas>>,
|
||||
TError = ErrorType<unknown>,
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<Awaited<ReturnType<typeof listIdeas>>, TError, TData>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getListIdeasQueryOptions(options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns counts of total, accepted and rejected ideas
|
||||
* @summary Get idea statistics
|
||||
*/
|
||||
export const getGetIdeaStatsUrl = () => {
|
||||
return `/api/ideas/stats`;
|
||||
};
|
||||
|
||||
export const getIdeaStats = async (
|
||||
options?: RequestInit,
|
||||
): Promise<IdeaStats> => {
|
||||
return customFetch<IdeaStats>(getGetIdeaStatsUrl(), {
|
||||
...options,
|
||||
method: "GET",
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetIdeaStatsQueryKey = () => {
|
||||
return [`/api/ideas/stats`] as const;
|
||||
};
|
||||
|
||||
export const getGetIdeaStatsQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getIdeaStats>>,
|
||||
TError = ErrorType<unknown>,
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getIdeaStats>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}) => {
|
||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getGetIdeaStatsQueryKey();
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getIdeaStats>>> = ({
|
||||
signal,
|
||||
}) => getIdeaStats({ signal, ...requestOptions });
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getIdeaStats>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetIdeaStatsQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getIdeaStats>>
|
||||
>;
|
||||
export type GetIdeaStatsQueryError = ErrorType<unknown>;
|
||||
|
||||
/**
|
||||
* @summary Get idea statistics
|
||||
*/
|
||||
|
||||
export function useGetIdeaStats<
|
||||
TData = Awaited<ReturnType<typeof getIdeaStats>>,
|
||||
TError = ErrorType<unknown>,
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getIdeaStats>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetIdeaStatsQueryOptions(options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the latest synthesized text of the people's voice
|
||||
* @summary Get current synthesis
|
||||
*/
|
||||
export const getGetSynthesisUrl = () => {
|
||||
return `/api/synthesis`;
|
||||
};
|
||||
|
||||
export const getSynthesis = async (
|
||||
options?: RequestInit,
|
||||
): Promise<Synthesis> => {
|
||||
return customFetch<Synthesis>(getGetSynthesisUrl(), {
|
||||
...options,
|
||||
method: "GET",
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetSynthesisQueryKey = () => {
|
||||
return [`/api/synthesis`] as const;
|
||||
};
|
||||
|
||||
export const getGetSynthesisQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getSynthesis>>,
|
||||
TError = ErrorType<unknown>,
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getSynthesis>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}) => {
|
||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getGetSynthesisQueryKey();
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getSynthesis>>> = ({
|
||||
signal,
|
||||
}) => getSynthesis({ signal, ...requestOptions });
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getSynthesis>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetSynthesisQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getSynthesis>>
|
||||
>;
|
||||
export type GetSynthesisQueryError = ErrorType<unknown>;
|
||||
|
||||
/**
|
||||
* @summary Get current synthesis
|
||||
*/
|
||||
|
||||
export function useGetSynthesis<
|
||||
TData = Awaited<ReturnType<typeof getSynthesis>>,
|
||||
TError = ErrorType<unknown>,
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getSynthesis>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetSynthesisQueryOptions(options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user