mirror of
https://github.com/System-End/scraps.git
synced 2026-04-19 18:35:20 +00:00
add in update and ai
This commit is contained in:
parent
22b8c354df
commit
1638d07b03
13 changed files with 306 additions and 14 deletions
2
backend/drizzle/0003_add_project_update_fields.sql
Normal file
2
backend/drizzle/0003_add_project_update_fields.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE "projects" ADD COLUMN "is_update" integer DEFAULT 0;--> statement-breakpoint
|
||||
ALTER TABLE "projects" ADD COLUMN "update_description" text;
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE "projects" DROP COLUMN IF EXISTS "is_update";--> statement-breakpoint
|
||||
ALTER TABLE "projects" ADD COLUMN "ai_description" text;
|
||||
|
|
@ -8,6 +8,20 @@
|
|||
"when": 1770422400000,
|
||||
"tag": "0002_feedback_and_activity",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"version": "7",
|
||||
"when": 1770508800000,
|
||||
"tag": "0003_add_project_update_fields",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 3,
|
||||
"version": "7",
|
||||
"when": 1770595200000,
|
||||
"tag": "0004_remove_is_update_add_ai_description",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -42,6 +42,8 @@ async function syncProjectsToAirtable(): Promise<void> {
|
|||
hoursOverride: projectsTable.hoursOverride,
|
||||
tier: projectsTable.tier,
|
||||
status: projectsTable.status,
|
||||
updateDescription: projectsTable.updateDescription,
|
||||
aiDescription: projectsTable.aiDescription,
|
||||
feedbackSource: projectsTable.feedbackSource,
|
||||
feedbackGood: projectsTable.feedbackGood,
|
||||
feedbackImprove: projectsTable.feedbackImprove,
|
||||
|
|
@ -117,9 +119,17 @@ async function syncProjectsToAirtable(): Promise<void> {
|
|||
const firstName = userInfo?.given_name || (project.username || '').split(' ')[0] || ''
|
||||
const lastName = userInfo?.family_name || (project.username || '').split(' ').slice(1).join(' ') || ''
|
||||
|
||||
const descriptionParts = [project.description || '']
|
||||
if (project.updateDescription) {
|
||||
descriptionParts.push(`\nThis project is an update. ${project.updateDescription}`)
|
||||
}
|
||||
if (project.aiDescription) {
|
||||
descriptionParts.push(`\nAI was used in this project. ${project.aiDescription}`)
|
||||
}
|
||||
|
||||
const fields: Airtable.FieldSet = {
|
||||
'Code URL': project.githubUrl,
|
||||
'Description': project.description || '',
|
||||
'Description': descriptionParts.join('\n'),
|
||||
'Email': project.email || '',
|
||||
'First Name': firstName,
|
||||
'Last Name': lastName,
|
||||
|
|
|
|||
|
|
@ -392,6 +392,9 @@ projects.get('/:id', async ({ params, headers }) => {
|
|||
status: project[0].status,
|
||||
scrapsAwarded: project[0].scrapsAwarded,
|
||||
views: project[0].views,
|
||||
updateDescription: project[0].updateDescription,
|
||||
aiDescription: isOwner ? project[0].aiDescription : undefined,
|
||||
usedAi: !!project[0].aiDescription,
|
||||
createdAt: project[0].createdAt,
|
||||
updatedAt: project[0].updatedAt
|
||||
},
|
||||
|
|
@ -413,6 +416,8 @@ projects.post('/', async ({ body, headers }) => {
|
|||
githubUrl?: string
|
||||
hackatimeProject?: string
|
||||
tier?: number
|
||||
updateDescription?: string
|
||||
aiDescription?: string
|
||||
}
|
||||
|
||||
if (!validateImageUrl(data.image)) {
|
||||
|
|
@ -437,7 +442,9 @@ projects.post('/', async ({ body, headers }) => {
|
|||
githubUrl: data.githubUrl || null,
|
||||
hackatimeProject: projectName || null,
|
||||
hours: 0,
|
||||
tier
|
||||
tier,
|
||||
updateDescription: data.updateDescription || null,
|
||||
aiDescription: data.aiDescription || null
|
||||
})
|
||||
.returning()
|
||||
|
||||
|
|
@ -482,6 +489,8 @@ projects.put('/:id', async ({ params, body, headers }) => {
|
|||
playableUrl?: string | null
|
||||
hackatimeProject?: string | null
|
||||
tier?: number
|
||||
updateDescription?: string | null
|
||||
aiDescription?: string | null
|
||||
}
|
||||
|
||||
if (!validateImageUrl(data.image)) {
|
||||
|
|
@ -511,6 +520,8 @@ projects.put('/:id', async ({ params, body, headers }) => {
|
|||
playableUrl: data.playableUrl,
|
||||
hackatimeProject: projectName,
|
||||
tier,
|
||||
updateDescription: data.updateDescription !== undefined ? (data.updateDescription || null) : undefined,
|
||||
aiDescription: data.aiDescription !== undefined ? (data.aiDescription || null) : undefined,
|
||||
updatedAt: new Date()
|
||||
})
|
||||
.where(and(eq(projectsTable.id, parseInt(params.id)), eq(projectsTable.userId, user.id)))
|
||||
|
|
|
|||
|
|
@ -22,6 +22,12 @@ export const projectsTable = pgTable('projects', {
|
|||
scrapsAwarded: integer('scraps_awarded').notNull().default(0),
|
||||
views: integer().notNull().default(0),
|
||||
|
||||
// Update fields
|
||||
updateDescription: text('update_description'),
|
||||
|
||||
// AI usage fields
|
||||
aiDescription: text('ai_description'),
|
||||
|
||||
// Feedback fields (filled on submission)
|
||||
feedbackSource: text('feedback_source'),
|
||||
feedbackGood: text('feedback_good'),
|
||||
|
|
|
|||
|
|
@ -60,6 +60,10 @@
|
|||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
let selectedTier = $state(1);
|
||||
let isUpdate = $state(false);
|
||||
let updateDescription = $state('');
|
||||
let usedAi = $state(false);
|
||||
let aiDescription = $state('');
|
||||
|
||||
const TIERS = [
|
||||
{ value: 1, description: $t.createProject.tierDescriptions.tier1 },
|
||||
|
|
@ -78,7 +82,9 @@
|
|||
description.trim().length >= DESC_MIN && description.trim().length <= DESC_MAX
|
||||
);
|
||||
let hasName = $derived(name.trim().length > 0 && name.trim().length <= NAME_MAX);
|
||||
let allRequirementsMet = $derived(hasDescription && hasName);
|
||||
let updateValid = $derived(!isUpdate || updateDescription.trim().length > 0);
|
||||
let aiValid = $derived(!usedAi || aiDescription.trim().length > 0);
|
||||
let allRequirementsMet = $derived(hasDescription && hasName && updateValid && aiValid);
|
||||
|
||||
async function fetchHackatimeProjects() {
|
||||
loadingProjects = true;
|
||||
|
|
@ -173,6 +179,10 @@
|
|||
selectedHackatimeProjects = [];
|
||||
showDropdown = false;
|
||||
selectedTier = 1;
|
||||
isUpdate = false;
|
||||
updateDescription = '';
|
||||
usedAi = false;
|
||||
aiDescription = '';
|
||||
error = null;
|
||||
}
|
||||
|
||||
|
|
@ -201,7 +211,9 @@
|
|||
image: imageUrl || null,
|
||||
githubUrl: finalGithubUrl,
|
||||
hackatimeProject: hackatimeValue,
|
||||
tier: selectedTier
|
||||
tier: selectedTier,
|
||||
updateDescription: isUpdate ? updateDescription : null,
|
||||
aiDescription: usedAi ? aiDescription : null
|
||||
})
|
||||
});
|
||||
|
||||
|
|
@ -465,6 +477,66 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Is Update Checkbox -->
|
||||
<div>
|
||||
<label class="flex cursor-pointer items-center gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={isUpdate}
|
||||
class="h-5 w-5 cursor-pointer accent-black"
|
||||
/>
|
||||
<span class="text-sm font-bold">{$t.createProject.isUpdateLabel}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{#if isUpdate}
|
||||
<div>
|
||||
<label for="updateDescription" class="mb-1 block text-sm font-bold"
|
||||
>{$t.createProject.whatDidYouUpdate} <span class="text-red-500">*</span></label
|
||||
>
|
||||
<textarea
|
||||
id="updateDescription"
|
||||
bind:value={updateDescription}
|
||||
rows="3"
|
||||
placeholder={$t.createProject.updateDescriptionPlaceholder}
|
||||
class="w-full resize-none rounded-lg border-2 border-black px-4 py-2 focus:border-dashed focus:outline-none"
|
||||
></textarea>
|
||||
{#if updateDescription.trim().length === 0}
|
||||
<p class="mt-1 text-xs text-red-500">{$t.createProject.pleaseDescribeUpdate}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- AI Usage Checkbox -->
|
||||
<div>
|
||||
<label class="flex cursor-pointer items-center gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={usedAi}
|
||||
class="h-5 w-5 cursor-pointer accent-black"
|
||||
/>
|
||||
<span class="text-sm font-bold">{$t.createProject.usedAiLabel}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{#if usedAi}
|
||||
<div>
|
||||
<label for="aiDescription" class="mb-1 block text-sm font-bold"
|
||||
>{$t.createProject.howWasAiUsed} <span class="text-red-500">*</span></label
|
||||
>
|
||||
<textarea
|
||||
id="aiDescription"
|
||||
bind:value={aiDescription}
|
||||
rows="3"
|
||||
placeholder={$t.createProject.aiDescriptionPlaceholder}
|
||||
class="w-full resize-none rounded-lg border-2 border-black px-4 py-2 focus:border-dashed focus:outline-none"
|
||||
></textarea>
|
||||
{#if aiDescription.trim().length === 0}
|
||||
<p class="mt-1 text-xs text-red-500">{$t.createProject.pleaseDescribeAiUsage}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Requirements Checklist -->
|
||||
<div class="rounded-lg border-2 border-black p-4">
|
||||
<p class="mb-3 font-bold">{$t.createProject.requirements}</p>
|
||||
|
|
|
|||
|
|
@ -269,7 +269,15 @@ export default {
|
|||
tier2: 'moderate complexity, multi-file projects',
|
||||
tier3: 'complex features, APIs, integrations',
|
||||
tier4: 'full applications, major undertakings'
|
||||
}
|
||||
},
|
||||
isUpdateLabel: 'this project is an update to a previous project',
|
||||
whatDidYouUpdate: 'what did you update?',
|
||||
updateDescriptionPlaceholder: 'Describe what you changed or improved in this update...',
|
||||
pleaseDescribeUpdate: 'Please describe what you updated',
|
||||
usedAiLabel: 'AI was used in this project',
|
||||
howWasAiUsed: 'how was AI used?',
|
||||
aiDescriptionPlaceholder: 'Describe how AI was used in this project...',
|
||||
pleaseDescribeAiUsage: 'Please describe how AI was used'
|
||||
},
|
||||
address: {
|
||||
shippingAddress: 'shipping address',
|
||||
|
|
@ -374,7 +382,17 @@ export default {
|
|||
unsubmitConfirmTitle: 'unsubmit project?',
|
||||
unsubmitConfirmMessage: 'this will remove your project from the review queue and return it to in-progress status.',
|
||||
unsubmitting: 'unsubmitting...',
|
||||
unsubmitSuccess: 'project unsubmitted successfully'
|
||||
unsubmitSuccess: 'project unsubmitted successfully',
|
||||
isUpdateLabel: 'this project is an update to a previous project',
|
||||
whatDidYouUpdate: 'what did you update?',
|
||||
updateDescriptionPlaceholder: 'Describe what you changed or improved in this update...',
|
||||
pleaseDescribeUpdate: 'Please describe what you updated',
|
||||
whatWasUpdated: 'what was updated',
|
||||
usedAiLabel: 'AI was used in this project',
|
||||
howWasAiUsed: 'how was AI used?',
|
||||
aiDescriptionPlaceholder: 'Describe how AI was used in this project...',
|
||||
pleaseDescribeAiUsage: 'Please describe how AI was used',
|
||||
aiWasUsed: 'AI was used in this project'
|
||||
},
|
||||
faq: {
|
||||
title: 'faq',
|
||||
|
|
|
|||
|
|
@ -269,7 +269,15 @@ export default {
|
|||
tier2: 'complejidad moderada, proyectos multi-archivo',
|
||||
tier3: 'características complejas, APIs, integraciones',
|
||||
tier4: 'aplicaciones completas, emprendimientos mayores'
|
||||
}
|
||||
},
|
||||
isUpdateLabel: 'este proyecto es una actualización de un proyecto anterior',
|
||||
whatDidYouUpdate: '¿qué actualizaste?',
|
||||
updateDescriptionPlaceholder: 'Describe qué cambiaste o mejoraste en esta actualización...',
|
||||
pleaseDescribeUpdate: 'Por favor describe qué actualizaste',
|
||||
usedAiLabel: 'se usó IA en este proyecto',
|
||||
howWasAiUsed: '¿cómo se usó la IA?',
|
||||
aiDescriptionPlaceholder: 'Describe cómo se usó la IA en este proyecto...',
|
||||
pleaseDescribeAiUsage: 'Por favor describe cómo se usó la IA'
|
||||
},
|
||||
address: {
|
||||
shippingAddress: 'dirección de envío',
|
||||
|
|
@ -414,7 +422,17 @@ export default {
|
|||
unsubmitConfirmTitle: '¿retirar proyecto?',
|
||||
unsubmitConfirmMessage: 'esto eliminará tu proyecto de la cola de revisión y lo devolverá al estado en progreso.',
|
||||
unsubmitting: 'retirando...',
|
||||
unsubmitSuccess: 'proyecto retirado exitosamente'
|
||||
unsubmitSuccess: 'proyecto retirado exitosamente',
|
||||
isUpdateLabel: 'este proyecto es una actualización de un proyecto anterior',
|
||||
whatDidYouUpdate: '¿qué actualizaste?',
|
||||
updateDescriptionPlaceholder: 'Describe qué cambiaste o mejoraste en esta actualización...',
|
||||
pleaseDescribeUpdate: 'Por favor describe qué actualizaste',
|
||||
whatWasUpdated: 'qué se actualizó',
|
||||
usedAiLabel: 'se usó IA en este proyecto',
|
||||
howWasAiUsed: '¿cómo se usó la IA?',
|
||||
aiDescriptionPlaceholder: 'Describe cómo se usó la IA en este proyecto...',
|
||||
pleaseDescribeAiUsage: 'Por favor describe cómo se usó la IA',
|
||||
aiWasUsed: 'se usó IA en este proyecto'
|
||||
},
|
||||
profile: {
|
||||
backToLeaderboard: 'volver a clasificación',
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@
|
|||
XCircle,
|
||||
Info,
|
||||
Globe,
|
||||
RefreshCw
|
||||
RefreshCw,
|
||||
Bot
|
||||
} from '@lucide/svelte';
|
||||
import ProjectPlaceholder from '$lib/components/ProjectPlaceholder.svelte';
|
||||
import { getUser } from '$lib/auth-client';
|
||||
|
|
@ -57,6 +58,8 @@
|
|||
feedbackSource: string | null;
|
||||
feedbackGood: string | null;
|
||||
feedbackImprove: string | null;
|
||||
updateDescription: string | null;
|
||||
aiDescription: string | null;
|
||||
}
|
||||
|
||||
interface User {
|
||||
|
|
@ -343,6 +346,24 @@
|
|||
</span>
|
||||
</div>
|
||||
<p class="mb-4 text-gray-600">{project.description}</p>
|
||||
{#if project.updateDescription}
|
||||
<div class="mb-4 rounded-lg border-2 border-dashed border-gray-400 bg-gray-50 p-4">
|
||||
<p class="mb-1 flex items-center gap-1.5 text-sm font-bold text-gray-600">
|
||||
<RefreshCw size={14} />
|
||||
{$t.project.whatWasUpdated}
|
||||
</p>
|
||||
<p class="text-gray-700">{project.updateDescription}</p>
|
||||
</div>
|
||||
{/if}
|
||||
{#if project.aiDescription}
|
||||
<div class="mb-4 rounded-lg border-2 border-dashed border-purple-400 bg-purple-50 p-4">
|
||||
<p class="mb-1 flex items-center gap-1.5 text-sm font-bold text-purple-600">
|
||||
<Bot size={14} />
|
||||
{$t.project.aiWasUsed}
|
||||
</p>
|
||||
<p class="text-purple-700">{project.aiDescription}</p>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex flex-wrap items-center gap-3 text-sm">
|
||||
<span class="rounded-full border-2 border-black bg-gray-100 px-3 py-1 font-bold"
|
||||
>{formatHours(project.hours)}h logged</span
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@
|
|||
Spool,
|
||||
Eye,
|
||||
RefreshCw,
|
||||
Undo2
|
||||
Undo2,
|
||||
Bot
|
||||
} from '@lucide/svelte';
|
||||
import { getUser } from '$lib/auth-client';
|
||||
import { API_URL } from '$lib/config';
|
||||
|
|
@ -42,6 +43,8 @@
|
|||
status: string;
|
||||
scrapsAwarded: number;
|
||||
views: number;
|
||||
updateDescription: string | null;
|
||||
usedAi: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
|
@ -278,12 +281,20 @@
|
|||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="mb-4 flex items-center gap-2">
|
||||
<div class="mb-4 flex flex-wrap items-center gap-2">
|
||||
<span
|
||||
class="rounded-full border-2 border-gray-400 bg-gray-100 px-3 py-1 text-sm font-bold text-gray-700"
|
||||
>
|
||||
{$t.project.tier.replace('{value}', String(project.tier))}
|
||||
</span>
|
||||
{#if project.usedAi}
|
||||
<span
|
||||
class="flex items-center gap-1 rounded-full border-2 border-purple-400 bg-purple-100 px-3 py-1 text-sm font-bold text-purple-700"
|
||||
>
|
||||
<Bot size={14} />
|
||||
{$t.project.aiWasUsed}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if project.description}
|
||||
|
|
@ -292,6 +303,16 @@
|
|||
<p class="mb-4 text-lg text-gray-400 italic">{$t.project.noDescriptionYet}</p>
|
||||
{/if}
|
||||
|
||||
{#if project.updateDescription}
|
||||
<div class="mb-4 rounded-lg border-2 border-dashed border-gray-400 bg-gray-50 p-4">
|
||||
<p class="mb-1 flex items-center gap-1.5 text-sm font-bold text-gray-600">
|
||||
<RefreshCw size={14} />
|
||||
{$t.project.whatWasUpdated}
|
||||
</p>
|
||||
<p class="text-gray-700">{project.updateDescription}</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="mb-3 flex flex-wrap items-center gap-3">
|
||||
<span
|
||||
class="flex items-center gap-2 rounded-full border-4 border-black bg-white px-4 py-2 font-bold"
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@
|
|||
hours: number;
|
||||
tier: number;
|
||||
status: string;
|
||||
updateDescription: string | null;
|
||||
aiDescription: string | null;
|
||||
}
|
||||
|
||||
const TIERS = [
|
||||
|
|
@ -52,6 +54,10 @@
|
|||
let loadingProjects = $state(false);
|
||||
let showDropdown = $state(false);
|
||||
let selectedTier = $state(1);
|
||||
let isUpdate = $state(false);
|
||||
let updateDescription = $state('');
|
||||
let usedAi = $state(false);
|
||||
let aiDescription = $state('');
|
||||
|
||||
const NAME_MAX = 50;
|
||||
const DESC_MIN = 20;
|
||||
|
|
@ -66,7 +72,9 @@
|
|||
);
|
||||
let githubValidation = $derived(validateGithubUrl(project?.githubUrl));
|
||||
let playableValidation = $derived(validatePlayableUrl(project?.playableUrl));
|
||||
let canSave = $derived(hasDescription && hasName && githubValidation.valid && playableValidation.valid);
|
||||
let updateValid = $derived(!isUpdate || updateDescription.trim().length > 0);
|
||||
let aiValid = $derived(!usedAi || aiDescription.trim().length > 0);
|
||||
let canSave = $derived(hasDescription && hasName && githubValidation.valid && playableValidation.valid && updateValid && aiValid);
|
||||
|
||||
onMount(async () => {
|
||||
const user = await getUser();
|
||||
|
|
@ -89,6 +97,10 @@
|
|||
project = responseData.project;
|
||||
imagePreview = project?.image || null;
|
||||
selectedTier = project?.tier || 1;
|
||||
isUpdate = !!(project?.updateDescription);
|
||||
updateDescription = project?.updateDescription || '';
|
||||
usedAi = !!(project?.aiDescription);
|
||||
aiDescription = project?.aiDescription || '';
|
||||
if (project?.hackatimeProject) {
|
||||
selectedHackatimeNames = project.hackatimeProject.split(',').map((p: string) => p.trim()).filter((p: string) => p.length > 0);
|
||||
}
|
||||
|
|
@ -216,7 +228,9 @@
|
|||
githubUrl: project.githubUrl,
|
||||
playableUrl: project.playableUrl,
|
||||
hackatimeProject: hackatimeValue,
|
||||
tier: selectedTier
|
||||
tier: selectedTier,
|
||||
updateDescription: isUpdate ? updateDescription : null,
|
||||
aiDescription: usedAi ? aiDescription : null
|
||||
})
|
||||
});
|
||||
|
||||
|
|
@ -505,6 +519,66 @@
|
|||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Is Update Checkbox -->
|
||||
<div>
|
||||
<label class="flex cursor-pointer items-center gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={isUpdate}
|
||||
class="h-5 w-5 cursor-pointer accent-black"
|
||||
/>
|
||||
<span class="text-sm font-bold">{$t.project.isUpdateLabel}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{#if isUpdate}
|
||||
<div>
|
||||
<label for="updateDescription" class="mb-2 block text-sm font-bold"
|
||||
>{$t.project.whatDidYouUpdate} <span class="text-red-500">*</span></label
|
||||
>
|
||||
<textarea
|
||||
id="updateDescription"
|
||||
bind:value={updateDescription}
|
||||
rows="3"
|
||||
placeholder={$t.project.updateDescriptionPlaceholder}
|
||||
class="w-full resize-none rounded-lg border-2 border-black px-4 py-3 focus:border-dashed focus:outline-none"
|
||||
></textarea>
|
||||
{#if updateDescription.trim().length === 0}
|
||||
<p class="mt-1 text-xs text-red-500">{$t.project.pleaseDescribeUpdate}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- AI Usage Checkbox -->
|
||||
<div>
|
||||
<label class="flex cursor-pointer items-center gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={usedAi}
|
||||
class="h-5 w-5 cursor-pointer accent-black"
|
||||
/>
|
||||
<span class="text-sm font-bold">{$t.project.usedAiLabel}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{#if usedAi}
|
||||
<div>
|
||||
<label for="aiDescription" class="mb-2 block text-sm font-bold"
|
||||
>{$t.project.howWasAiUsed} <span class="text-red-500">*</span></label
|
||||
>
|
||||
<textarea
|
||||
id="aiDescription"
|
||||
bind:value={aiDescription}
|
||||
rows="3"
|
||||
placeholder={$t.project.aiDescriptionPlaceholder}
|
||||
class="w-full resize-none rounded-lg border-2 border-black px-4 py-3 focus:border-dashed focus:outline-none"
|
||||
></textarea>
|
||||
{#if aiDescription.trim().length === 0}
|
||||
<p class="mt-1 text-xs text-red-500">{$t.project.pleaseDescribeAiUsage}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@
|
|||
Clock,
|
||||
CheckCircle,
|
||||
AlertTriangle,
|
||||
Package
|
||||
Package,
|
||||
RefreshCw,
|
||||
Bot
|
||||
} from '@lucide/svelte';
|
||||
import { API_URL } from '$lib/config';
|
||||
import { formatHours } from '$lib/utils';
|
||||
|
|
@ -26,6 +28,8 @@
|
|||
playableUrl: string | null;
|
||||
hours: number;
|
||||
status: string;
|
||||
updateDescription: string | null;
|
||||
usedAi: boolean;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
|
|
@ -131,6 +135,25 @@
|
|||
{/if}
|
||||
</div>
|
||||
<p class="mb-4 text-gray-600">{project.description}</p>
|
||||
{#if project.updateDescription}
|
||||
<div class="mb-4 rounded-lg border-2 border-dashed border-gray-400 bg-gray-50 p-4">
|
||||
<p class="mb-1 flex items-center gap-1.5 text-sm font-bold text-gray-600">
|
||||
<RefreshCw size={14} />
|
||||
{$t.project.whatWasUpdated}
|
||||
</p>
|
||||
<p class="text-gray-700">{project.updateDescription}</p>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="mb-4 flex flex-wrap items-center gap-2">
|
||||
{#if project.usedAi}
|
||||
<span
|
||||
class="flex items-center gap-1 rounded-full border-2 border-purple-400 bg-purple-100 px-3 py-1 text-sm font-bold text-purple-700"
|
||||
>
|
||||
<Bot size={14} />
|
||||
{$t.project.aiWasUsed}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-3 text-sm">
|
||||
<span
|
||||
class="flex items-center gap-1 rounded-full border-2 border-black bg-gray-100 px-3 py-1 font-bold"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue