Skip to content
This repository was archived by the owner on Jul 2, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "viralmind-desktop",
"version": "0.1.3",
"version": "0.1.4",
"description": "",
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "viralmind-desktop"
version = "0.1.3"
version = "0.1.4"
description = "The Viralmind desktop app."
authors = ["Dillon DuPont", "Morgan Dean"]
edition = "2021"
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Viralmind Desktop",
"version": "0.1.3",
"version": "0.1.4",
"identifier": "ai.viralmind.desktop",
"build": {
"beforeDevCommand": "bun run dev",
Expand Down
9 changes: 9 additions & 0 deletions src/lib/api/endpoints/forge/pools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,12 @@ export async function createPoolWithApps(
): Promise<TrainingPool> {
return apiClient.post<TrainingPool>('/forge/pools', input, { requiresAuth: true });
}

/**
* Update a pool with an email address
* @param email The email address
* @returns Promise resolving to void
*/
export async function updatePoolEmail(poolID: string, email: string): Promise<void> {
return apiClient.put('/forge/pools/email', { id: poolID, email }, { requiresAuth: true });
}
51 changes: 51 additions & 0 deletions src/lib/api/endpoints/gym/leaderboards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { apiClient } from '$lib/api';

/**
* Get gym leaderboard data
* @returns Promise resolving to the leaderboard data
*/
export async function getLeaderboardData(): Promise<{
workersLeaderboard: {
rank: number;
address: string;
nickname?: string;
tasks: number;
rewards: number;
avgScore: number;
}[];
forgeLeaderboard: {
rank: number;
name: string;
tasks: number;
payout: number;
}[];
stats: {
totalWorkers: number;
tasksCompleted: number;
totalRewards: number;
activeForges: number;
};
}> {
return apiClient.get<{
workersLeaderboard: {
rank: number;
address: string;
nickname?: string;
tasks: number;
rewards: number;
avgScore: number;
}[];
forgeLeaderboard: {
rank: number;
name: string;
tasks: number;
payout: number;
}[];
stats: {
totalWorkers: number;
tasksCompleted: number;
totalRewards: number;
activeForges: number;
};
}>('/gym/leaderboards');
}
16 changes: 6 additions & 10 deletions src/lib/api/endpoints/gym/quests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,12 @@ export async function generateQuest(

// If poolId is provided, get reward info
if (poolId) {
try {
const rewardInfo = await getReward(poolId, taskId);
quest.pool_id = poolId;
quest.reward = { time: rewardInfo.time, max_reward: rewardInfo.maxReward };
// If taskId is provided, add it to the quest
if (taskId) {
quest.task_id = taskId;
}
} catch (error) {
console.error('Failed to get reward info:', error);
quest.pool_id = poolId;
const rewardInfo = await getReward(poolId, taskId);
quest.reward = { time: rewardInfo.time, max_reward: rewardInfo.maxReward };
// If taskId is provided, add it to the quest
if (taskId) {
quest.task_id = taskId;
}
}

Expand Down
12 changes: 12 additions & 0 deletions src/lib/api/endpoints/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,15 @@ export async function checkConnection(token: string): Promise<{
address?: string;
}>('/wallet/connection', { token });
}

/**
* Generate apps from a prompt
* @param prompt The prompt to generate apps from
* @returns Promise resolving to the generated content
*/
export async function getNickname(address: string): Promise<string> {
return apiClient.get<string>('/wallet/nickname', { address });
}
export async function setNickname(address: string, nickname: string): Promise<string> {
return apiClient.put<string>('/wallet/nickname', { address, nickname }, { requiresAuth: true });
}
10 changes: 5 additions & 5 deletions src/lib/components/Sidebar.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { Dumbbell, Hammer, Square, LoaderCircle } from 'lucide-svelte';
import { Dumbbell, Hammer, Square, LoaderCircle, Trophy } from 'lucide-svelte';
import { page } from '$app/state';
import logo from '$lib/assets/Logo_Icon.png';
import WalletButton from './WalletButton.svelte';
Expand All @@ -9,8 +9,8 @@
import { recordingState } from '$lib/stores/recording';

const earnButtons = [
{ path: '/app/gym', icon: Dumbbell, label: 'Gym' }
// { path: "/app/node", icon: Server, label: "Node" },
{ path: '/app/gym', icon: Dumbbell, label: 'Gym' },
{ path: '/app/leaderboards', icon: Trophy, label: 'Leaderboards' }
];

const spendButtons = [
Expand Down Expand Up @@ -39,7 +39,7 @@
}
</script>

<div class="w-16 flex flex-col bg-transparent py-2 pl-2 space-y-4">
<div class="w-16 flex flex-col bg-transparent pt-2 pb-6 pl-2 space-y-4">
<div class="flex-start grow flex flex-col gap-2">
<div class="my-4 flex justify-center">
<img src={logo} alt="ViralMind Logo" class="h-8 w-8 object-contain" />
Expand Down Expand Up @@ -70,7 +70,7 @@
{@const Icon = button.icon}
<a
href={button.path}
class="w-full py-2 flex justify-center rounded-full transition-colors {page.url.pathname.split(
class="w-full my-1 py-2 flex justify-center rounded-full transition-colors {page.url.pathname.split(
'/'
)[2] == button.path.split('/')[2]
? 'bg-secondary-300 text-white'
Expand Down
44 changes: 34 additions & 10 deletions src/lib/components/WalletButton.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,25 @@
isConnecting,
getConnectionUrl,
startPolling,
disconnectWallet
disconnectWallet,
nickname
} from '$lib/stores/wallet';

export let variant: 'compact' | 'large' = 'compact';
export let theme: 'dark' | 'light' = 'dark';
import { LogOut, Wallet, ExternalLink, Coins } from 'lucide-svelte';
import { onMount } from 'svelte';
import { getBalance, listSubmissions } from '$lib/api/endpoints/forge';
import type { SubmissionStatus } from '$lib/types/forge';
import { getNickname } from '$lib/api/endpoints/wallet';
import NicknameModal from './modals/NicknameModal.svelte';

let viralBalance = 0;
let recentSubmissions: SubmissionStatus[] = [];
const {
variant = 'compact',
theme = 'dark'
}: { variant?: 'compact' | 'large'; theme?: 'dark' | 'light' } = $props();

let viralBalance = $state(0);
let nickNameModalOpen = $state(false);
let recentSubmissions: SubmissionStatus[] = $state([]);

function formatNumber(num: number): string {
return num.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
Expand Down Expand Up @@ -45,16 +52,20 @@

onMount(async () => {
if ($walletAddress) {
getBalance($walletAddress);
viralBalance = await getBalance($walletAddress);
$nickname = await getNickname($walletAddress);
loadSubmissions();
}
});

// Subscribe to wallet address changes
$: if ($walletAddress) {
getBalance($walletAddress);
loadSubmissions();
}
walletAddress.subscribe(async (address) => {
if (address) {
viralBalance = await getBalance(address);
$nickname = await getNickname(address);
loadSubmissions();
}
});
</script>

{#if $walletAddress}
Expand Down Expand Up @@ -84,6 +95,17 @@
<span class="text-gray-400">Wallet</span>
<span class="font-mono">{$walletAddress.slice(0, 4)}...{$walletAddress.slice(-4)}</span>
</div>
<!-- Nickname -->
<div class="flex justify-between items-center">
<span class="text-gray-400">Nickname</span>
<button
onclick={() => (nickNameModalOpen = true)}
class=" {$nickname
? ''
: 'text-gray-500'} font-semibold hover:text-white transition-all hover:underline">
{$nickname || 'Set Your Nickname'}
</button>
</div>

<!-- Balance -->
<div class="flex justify-between items-center">
Expand Down Expand Up @@ -166,3 +188,5 @@
{/if}
</a>
{/if}

<NicknameModal bind:open={nickNameModalOpen} oldNick={$nickname} />
1 change: 1 addition & 0 deletions src/lib/components/form/Button.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
children: Snippet;
variant?: 'primary' | 'destroy' | 'warning' | 'secondary' | 'green';
behavior?: 'invert' | 'none';
rest?: HTMLButtonAttributes;
};

let {
Expand Down
17 changes: 12 additions & 5 deletions src/lib/components/form/Input.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,22 @@
});
</script>

<div class="relative group">
<div
class="absolute text-gray-400 group-focus-within:text-secondary-300 inset-y-0 start-0 flex transition-all items-center ps-3 pointer-events-none">
{@render icon?.()}
{#if icon}
<div class="relative group">
<div
class="absolute text-gray-400 group-focus-within:text-secondary-300 inset-y-0 start-0 flex transition-all items-center ps-3 pointer-events-none">
{@render icon?.()}
</div>
<input
class="{variantClasses} w-full ps-12 py-2 px-3 rounded-lg border focus:outline-none focus:ring transition-all {className}"
bind:value
{...rest} />
</div>
{:else}
<input
class="{variantClasses} w-full {icon
? 'ps-12'
: ''} py-2 px-3 rounded-lg border focus:outline-none focus:ring transition-all {className}"
bind:value
{...rest} />
</div>
{/if}
32 changes: 22 additions & 10 deletions src/lib/components/gym-builder/GymBuilderView.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script lang="ts">
import Button from '$lib/components/form/Button.svelte';
import GenerateGymModal from '$lib/components/modals/GenerateGymModal.svelte';
import { Upload, ListTodo, Sliders, LayoutDashboard } from 'lucide-svelte';
import { Upload, ListTodo, Sliders, LayoutDashboard, LoaderCircle } from 'lucide-svelte';
import { TrainingPoolStatus, type TrainingPool, type PoolSubmission } from '$lib/types/forge';

// Import our tab components
Expand All @@ -27,6 +27,7 @@
let activeTab = 'overview';
let unsavedChanges = false;
let showSkillsModal = false;
let savingChanges = false;
let submissions: PoolSubmission[] = [];

// Reference to the TasksTab component to access its apps array
Expand All @@ -36,6 +37,7 @@

async function handleSaveChanges() {
try {
savingChanges = true;
// Create an updates object to hold all changes
const updates: any = {};

Expand Down Expand Up @@ -90,6 +92,8 @@
} catch (error) {
console.error('Failed to save changes:', error);
alert('Failed to save changes. Please try again.');
} finally {
savingChanges = false;
}
}

Expand Down Expand Up @@ -268,24 +272,32 @@
{#if activeTab === 'overview'}
<OverviewTab {pool} />
{:else if activeTab === 'settings'}
<SettingsTab
{pool}
{onSave}
{onRefresh}
{refreshingPools}
{unsavedChanges}
{handleSaveChanges} />
<SettingsTab {pool} {onRefresh} {refreshingPools} bind:unsavedChanges />
{:else if activeTab === 'tasks'}
<TasksTab
bind:this={tasksTabComponent}
bind:apps={tasksApps}
{pool}
bind:unsavedChanges
{regenerateTasks}
{handleSaveChanges} />
{regenerateTasks} />
{:else if activeTab === 'uploads'}
<UploadsTab {pool} bind:submissions />
{/if}

<div class="flex py-2 flex-col gap-2">
{#if unsavedChanges}
<Button
class="w-full justify-center border-green-500! hover:border-green-600! bg-green-500! text-white! hover:bg-green-600!"
disabled={savingChanges}
onclick={handleSaveChanges}>
{#if !savingChanges}
Save Changes
{:else}
<LoaderCircle class="animate-spin w-8 h-8 mx-auto" />
{/if}
</Button>
{/if}
</div>
</div>
</div>

Expand Down
Loading
Loading