Skip to content

Commit 63b7a58

Browse files
Merge pull request #379 from CivicDataLab/dev
Release 0.3
2 parents 3f654ae + a96ee76 commit 63b7a58

181 files changed

Lines changed: 22614 additions & 9484 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.local.example

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,15 @@ FEATURE_SITEMAPS='false'
3434
FEATURE_SITEMAP_BACKEND_BASE_URL= http://backendurl/api
3535
FEATURE_SITEMAP_ITEMS_PER_PAGE=5
3636
FEATURE_SITEMAP_CACHE_DURATION=3600
37-
FEATURE_SITEMAP_CHILD_CACHE_DURATION=21600
37+
FEATURE_SITEMAP_CHILD_CACHE_DURATION=21600
38+
39+
40+
41+
NODE_ENV=development
42+
43+
# Monitoring
44+
NEXT_PUBLIC_GRAPHQL_INTROSPECTION=false
45+
NEXT_TELEMETRY_DISABLED=1
46+
47+
# Turbopack (suppress Sentry warning when using --turbo)
48+
SENTRY_SUPPRESS_TURBOPACK_WARNING=1

.eslintrc.js

Lines changed: 0 additions & 20 deletions
This file was deleted.

.eslintrc.json

Lines changed: 0 additions & 10 deletions
This file was deleted.

app/[locale]/(user)/about-us/page.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { Text } from 'opub-ui';
44
import { generateJsonLd, generatePageMetadata } from '@/lib/utils';
55
import BreadCrumbs from '@/components/BreadCrumbs';
66
import JsonLd from '@/components/JsonLd';
7-
import Team from './components/Team';
87

98
export const generateMetadata = () =>
109
generatePageMetadata({
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
'use client';
2+
3+
import { graphql } from '@/gql';
4+
import { useQuery } from '@tanstack/react-query';
5+
import { Spinner, Text } from 'opub-ui';
6+
7+
import BreadCrumbs from '@/components/BreadCrumbs';
8+
import JsonLd from '@/components/JsonLd';
9+
import { GraphQL } from '@/lib/api';
10+
import { generateJsonLd } from '@/lib/utils';
11+
import Metadata from './components/Metadata';
12+
import PrimaryData from './components/PrimaryData';
13+
import Versions from './components/Versions';
14+
15+
const aiModelQuery: any = graphql(`
16+
query getAIModel($modelId: Int!) {
17+
getAiModel(modelId: $modelId) {
18+
id
19+
name
20+
displayName
21+
description
22+
modelType
23+
domain
24+
metadata
25+
status
26+
isPublic
27+
isActive
28+
createdAt
29+
updatedAt
30+
tags {
31+
id
32+
value
33+
}
34+
sectors {
35+
id
36+
name
37+
}
38+
geographies {
39+
id
40+
name
41+
}
42+
organization {
43+
id
44+
name
45+
logo {
46+
url
47+
}
48+
}
49+
user {
50+
id
51+
fullName
52+
profilePicture {
53+
url
54+
}
55+
}
56+
versions {
57+
id
58+
version
59+
versionNotes
60+
lifecycleStage
61+
isLatest
62+
supportsStreaming
63+
maxTokens
64+
supportedLanguages
65+
status
66+
createdAt
67+
updatedAt
68+
providers {
69+
id
70+
provider
71+
providerModelId
72+
isPrimary
73+
isActive
74+
# API Configuration
75+
apiEndpointUrl
76+
apiHttpMethod
77+
apiTimeoutSeconds
78+
apiAuthType
79+
# HuggingFace Configuration
80+
hfUsePipeline
81+
hfModelClass
82+
framework
83+
}
84+
}
85+
}
86+
}
87+
`);
88+
89+
export default function AIModelDetailsPage({
90+
modelId,
91+
}: {
92+
modelId: string;
93+
}) {
94+
const { data, isLoading, error } = useQuery(
95+
[`aimodel_details_${modelId}`],
96+
() => GraphQL(aiModelQuery, {}, { modelId: parseInt(modelId) }),
97+
{
98+
retry: false,
99+
onError: (err) => {
100+
console.error('Error fetching AI model:', err);
101+
},
102+
}
103+
);
104+
105+
const modelData = (data as any)?.getAiModel;
106+
107+
const jsonLd = generateJsonLd({
108+
'@context': 'https://schema.org',
109+
'@type': 'SoftwareApplication',
110+
name: modelData?.displayName || modelData?.name,
111+
url: `${process.env.NEXT_PUBLIC_PLATFORM_URL}/aimodels/${modelId}`,
112+
description: modelData?.description,
113+
applicationCategory: 'AI Model',
114+
publisher: {
115+
'@type': 'Organization',
116+
name: 'CivicDataSpace',
117+
url: `${process.env.NEXT_PUBLIC_PLATFORM_URL}`,
118+
},
119+
});
120+
121+
return (
122+
<>
123+
<JsonLd json={jsonLd} />
124+
<main className="bg-surfaceDefault">
125+
<BreadCrumbs
126+
data={[
127+
{ href: '/', label: 'Home' },
128+
{ href: '/search?types=aimodel', label: 'AI Models' },
129+
{ href: '#', label: 'AI Model Details' },
130+
]}
131+
/>
132+
<div className="flex">
133+
<div className="w-full gap-10 border-r-2 border-solid border-greyExtralight p-6 lg:w-3/4 lg:p-10">
134+
{isLoading ? (
135+
<div className="mt-8 flex justify-center">
136+
<Spinner />
137+
</div>
138+
) : error ? (
139+
<div className="mt-8 flex flex-col items-center gap-4">
140+
<Text variant="heading2xl">Error loading AI Model</Text>
141+
<Text variant="bodyMd" className="text-textSubdued">
142+
{error instanceof Error ? error.message : 'Failed to fetch AI model'}
143+
</Text>
144+
<Text variant="bodySm" className="text-textSubdued">
145+
Model ID: {modelId}
146+
</Text>
147+
</div>
148+
) : modelData ? (
149+
<>
150+
<PrimaryData data={modelData} isLoading={isLoading} />
151+
<Versions data={modelData} />
152+
</>
153+
) : (
154+
<div className="mt-8 flex justify-center">
155+
<Text variant="heading2xl">AI Model not found</Text>
156+
</div>
157+
)}
158+
</div>
159+
<div className="hidden w-1/4 gap-10 px-7 py-10 lg:block">
160+
{isLoading ? (
161+
<div className="mt-8 flex justify-center">
162+
<Spinner />
163+
</div>
164+
) : (
165+
modelData && <Metadata data={modelData} />
166+
)}
167+
</div>
168+
</div>
169+
</main>
170+
</>
171+
);
172+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
'use client';
2+
3+
import { Text } from 'opub-ui';
4+
5+
interface DetailsProps {
6+
data: any;
7+
}
8+
9+
export default function Details({ data }: DetailsProps) {
10+
if (!data) return null;
11+
12+
return (
13+
<div className="mt-8 flex flex-col gap-6">
14+
{/* Input/Output Schema */}
15+
{(data.inputSchema && Object.keys(data.inputSchema).length > 0) ||
16+
(data.outputSchema && Object.keys(data.outputSchema).length > 0) ? (
17+
<div className="flex flex-col gap-4">
18+
<Text variant="headingLg" fontWeight="semibold">
19+
API Schema
20+
</Text>
21+
22+
{data.inputSchema && Object.keys(data.inputSchema).length > 0 && (
23+
<div className="flex flex-col gap-2">
24+
<Text variant="bodyMd" fontWeight="semibold">
25+
Input Schema
26+
</Text>
27+
<pre className="overflow-x-auto rounded-lg border border-greyExtralight bg-surfaceSubdued p-4 text-sm">
28+
{JSON.stringify(data.inputSchema, null, 2)}
29+
</pre>
30+
</div>
31+
)}
32+
33+
{data.outputSchema && Object.keys(data.outputSchema).length > 0 && (
34+
<div className="flex flex-col gap-2">
35+
<Text variant="bodyMd" fontWeight="semibold">
36+
Output Schema
37+
</Text>
38+
<pre className="overflow-x-auto rounded-lg border border-greyExtralight bg-surfaceSubdued p-4 text-sm">
39+
{JSON.stringify(data.outputSchema, null, 2)}
40+
</pre>
41+
</div>
42+
)}
43+
</div>
44+
) : null}
45+
46+
{/* Additional Metadata */}
47+
{data.metadata && Object.keys(data.metadata).length > 0 && (
48+
<div className="flex flex-col gap-3">
49+
<Text variant="headingLg" fontWeight="semibold">
50+
Additional Information
51+
</Text>
52+
<div className="rounded-lg border border-greyExtralight p-4">
53+
{Object.entries(data.metadata).map(([key, value]) => (
54+
<div key={key} className="mb-3 last:mb-0">
55+
<Text variant="bodySm" className="text-textSubdued">
56+
{key.replace(/_/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase())}
57+
</Text>
58+
<Text variant="bodyMd" className="mt-1">
59+
{typeof value === 'object' ? JSON.stringify(value, null, 2) : String(value)}
60+
</Text>
61+
</div>
62+
))}
63+
</div>
64+
</div>
65+
)}
66+
67+
{/* Endpoints Information */}
68+
{data.endpoints && data.endpoints.length > 0 && (
69+
<div className="flex flex-col gap-3">
70+
<Text variant="headingLg" fontWeight="semibold">
71+
API Endpoints
72+
</Text>
73+
<div className="flex flex-col gap-3">
74+
{data.endpoints.map((endpoint: any, index: number) => (
75+
<div
76+
key={endpoint.id || index}
77+
className="rounded-lg border border-greyExtralight p-4"
78+
>
79+
<div className="mb-2 flex items-center gap-2">
80+
<Text variant="bodyMd" fontWeight="semibold">
81+
{endpoint.url}
82+
</Text>
83+
{endpoint.isPrimary && (
84+
<span className="rounded bg-primaryBlue px-2 py-1 text-xs text-white">
85+
Primary
86+
</span>
87+
)}
88+
</div>
89+
<div className="grid gap-2 text-sm">
90+
<div>
91+
<span className="text-textSubdued">Method: </span>
92+
<span>{endpoint.httpMethod}</span>
93+
</div>
94+
<div>
95+
<span className="text-textSubdued">Auth Type: </span>
96+
<span>{endpoint.authType}</span>
97+
</div>
98+
{endpoint.timeoutSeconds && (
99+
<div>
100+
<span className="text-textSubdued">Timeout: </span>
101+
<span>{endpoint.timeoutSeconds}s</span>
102+
</div>
103+
)}
104+
<div>
105+
<span className="text-textSubdued">Status: </span>
106+
<span>{endpoint.isActive ? 'Active' : 'Inactive'}</span>
107+
</div>
108+
</div>
109+
</div>
110+
))}
111+
</div>
112+
</div>
113+
)}
114+
</div>
115+
);
116+
}

0 commit comments

Comments
 (0)