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:
pironantoine
2026-04-03 16:25:11 +00:00
parent 4d26b95657
commit f9c4073d21
92 changed files with 8199 additions and 23 deletions
@@ -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;
}
+319 -3
View File
@@ -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 };
}