diff --git a/package.json b/package.json index 51e241e..13bc858 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "viralmind-desktop", - "version": "0.1.3", + "version": "0.1.4", "description": "", "type": "module", "scripts": { diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 69bded7..6ac619e 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -6597,7 +6597,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "viralmind-desktop" -version = "0.1.3" +version = "0.1.4" dependencies = [ "app-finder", "base64 0.21.7", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 970faee..f980060 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -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" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index f06e64d..afe5a6e 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -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", diff --git a/src/lib/api/endpoints/forge/pools.ts b/src/lib/api/endpoints/forge/pools.ts index 3bd2327..7252524 100644 --- a/src/lib/api/endpoints/forge/pools.ts +++ b/src/lib/api/endpoints/forge/pools.ts @@ -88,3 +88,12 @@ export async function createPoolWithApps( ): Promise { return apiClient.post('/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 { + return apiClient.put('/forge/pools/email', { id: poolID, email }, { requiresAuth: true }); +} diff --git a/src/lib/api/endpoints/gym/leaderboards.ts b/src/lib/api/endpoints/gym/leaderboards.ts new file mode 100644 index 0000000..6f40ed1 --- /dev/null +++ b/src/lib/api/endpoints/gym/leaderboards.ts @@ -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'); +} diff --git a/src/lib/api/endpoints/gym/quests.ts b/src/lib/api/endpoints/gym/quests.ts index 8eac6e2..93d5688 100644 --- a/src/lib/api/endpoints/gym/quests.ts +++ b/src/lib/api/endpoints/gym/quests.ts @@ -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; } } diff --git a/src/lib/api/endpoints/wallet.ts b/src/lib/api/endpoints/wallet.ts index 91a4f28..7e29b90 100644 --- a/src/lib/api/endpoints/wallet.ts +++ b/src/lib/api/endpoints/wallet.ts @@ -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 { + return apiClient.get('/wallet/nickname', { address }); +} +export async function setNickname(address: string, nickname: string): Promise { + return apiClient.put('/wallet/nickname', { address, nickname }, { requiresAuth: true }); +} diff --git a/src/lib/components/Sidebar.svelte b/src/lib/components/Sidebar.svelte index 72d9113..969677d 100644 --- a/src/lib/components/Sidebar.svelte +++ b/src/lib/components/Sidebar.svelte @@ -1,5 +1,5 @@ -
+
+ +
+ Nickname + +
@@ -166,3 +188,5 @@ {/if} {/if} + + diff --git a/src/lib/components/form/Button.svelte b/src/lib/components/form/Button.svelte index c04ff3c..cc6cf07 100644 --- a/src/lib/components/form/Button.svelte +++ b/src/lib/components/form/Button.svelte @@ -9,6 +9,7 @@ children: Snippet; variant?: 'primary' | 'destroy' | 'warning' | 'secondary' | 'green'; behavior?: 'invert' | 'none'; + rest?: HTMLButtonAttributes; }; let { diff --git a/src/lib/components/form/Input.svelte b/src/lib/components/form/Input.svelte index e841b35..0fd65e5 100644 --- a/src/lib/components/form/Input.svelte +++ b/src/lib/components/form/Input.svelte @@ -35,15 +35,22 @@ }); -
-
- {@render icon?.()} +{#if icon} +
+
+ {@render icon?.()} +
+
+{:else} -
+{/if} diff --git a/src/lib/components/gym-builder/GymBuilderView.svelte b/src/lib/components/gym-builder/GymBuilderView.svelte index 0918df8..ab7a8e4 100644 --- a/src/lib/components/gym-builder/GymBuilderView.svelte +++ b/src/lib/components/gym-builder/GymBuilderView.svelte @@ -1,7 +1,7 @@
- -
-

{pool.name}

-
- - -
-
-
Total Demonstrations
-
{pool.demonstrations.toLocaleString()}
-
- -
-
Pool Balance
-
{formatNumber(pool.funds)} {pool.token.symbol}
+ +
+
+

{pool.name}

- -
-
Reward Per Demo
-
{pool.pricePerDemo || 1} {pool.token.symbol}
+ + +
+
+
Total Demonstrations
+
{pool.demonstrations.toLocaleString()}
+
+
+
Reward Per Demo
+
+ {pool.pricePerDemo || 1} + {pool.token.symbol} +
+
+ +
+
Pool Balance
+
+ {formatNumber(pool.funds)} + {pool.token.symbol} +
+
+
+
Gas Balance
+
+ {formatNumber(pool.solBalance)} + SOL +
+
- + -
+

Deposit Address

+
+
+

Notifications

+

Get an email when your gym runs out of $VIRAL or SOL for gas fees.

+
{ + e.preventDefault(); + if (loading) return; + saveEmail(); + }} + class="w-full flex gap-2"> + + +
+
+
- + {#if pool.status === TrainingPoolStatus.noGas || pool.status === TrainingPoolStatus.noFunds}
@@ -65,22 +148,21 @@
{#if pool.status === TrainingPoolStatus.noGas} -

- Insufficient SOL for Gas -

+

Insufficient SOL for Gas

- Your gym needs SOL to pay for on-chain transactions. Without gas, the gym cannot function on the Solana blockchain. + Your gym needs SOL to pay for on-chain transactions. Without gas, the gym cannot + function on the Solana blockchain.

{:else if pool.status === TrainingPoolStatus.noFunds} -

- Insufficient VIRAL Tokens -

+

Insufficient VIRAL Tokens

- Your gym needs VIRAL tokens to reward users who provide demonstrations. Without funds, users won't receive compensation. + Your gym needs VIRAL tokens to reward users who provide demonstrations. Without funds, + users won't receive compensation.

{/if}

- Deposit {pool.status === TrainingPoolStatus.noGas ? 'SOL' : 'VIRAL'} to the address above to activate your gym and start collecting data. + Deposit {pool.status === TrainingPoolStatus.noGas ? 'SOL' : 'VIRAL'} to the address above to + activate your gym and start collecting data.

diff --git a/src/lib/components/gym-builder/SettingsTab.svelte b/src/lib/components/gym-builder/SettingsTab.svelte index 972b12b..1b6178b 100644 --- a/src/lib/components/gym-builder/SettingsTab.svelte +++ b/src/lib/components/gym-builder/SettingsTab.svelte @@ -16,11 +16,9 @@ unsavedName?: boolean; unsavedUploadLimit?: boolean; }; - export let onSave: (pool: TrainingPool, updates: any) => Promise; export let onRefresh: (poolId: string) => Promise; export let refreshingPools: Set; export let unsavedChanges: boolean; - export let handleSaveChanges: () => Promise; // Upload limit stats let currentCount = 0; @@ -268,15 +266,16 @@ Price per Demonstration
- - {pool.token.symbol} + {pool.token.symbol}

Minimum price: 1 {pool.token.symbol} @@ -487,37 +486,4 @@

- -
- {#if unsavedChanges} - - {/if} - - -
diff --git a/src/lib/components/gym-builder/TasksTab.svelte b/src/lib/components/gym-builder/TasksTab.svelte index 4f9add3..ea4edee 100644 --- a/src/lib/components/gym-builder/TasksTab.svelte +++ b/src/lib/components/gym-builder/TasksTab.svelte @@ -1,24 +1,24 @@ + +{#if open} +
+
+

Set Nickname

+

+ Set your nickname to be displayed on the demonstrator leaderboard. +

+

Maximum length of 25 characters. Make it memorable!

+ +
+ + +
+
+
+{/if} diff --git a/src/lib/stores/wallet.ts b/src/lib/stores/wallet.ts index d15624e..5e8ae75 100644 --- a/src/lib/stores/wallet.ts +++ b/src/lib/stores/wallet.ts @@ -10,6 +10,7 @@ const storedToken = typeof localStorage !== 'undefined' ? localStorage.getItem('connectionToken') : null; export const walletAddress = writable(storedAddress); +export const nickname = writable(''); export const connectionToken = writable(storedToken); export const isConnecting = writable(false); diff --git a/src/lib/types/forge.ts b/src/lib/types/forge.ts index 75cb887..cc281d5 100644 --- a/src/lib/types/forge.ts +++ b/src/lib/types/forge.ts @@ -67,8 +67,10 @@ export interface TrainingPool { status: TrainingPoolStatus; demonstrations: number; funds: number; + solBalance: number; token: Token; skills: string; + ownerEmail?: string; ownerAddress: string; depositAddress: string; expanded?: boolean; diff --git a/src/routes/app/+layout.svelte b/src/routes/app/+layout.svelte index 3636940..1eae373 100644 --- a/src/routes/app/+layout.svelte +++ b/src/routes/app/+layout.svelte @@ -1,6 +1,7 @@ + +
+
+
+

Leaderboards

+
+ + +
+
+ + +
+
+ + {#if !loading} +
+ + +
+
Rank
+
+ {activeTab === 'workers' ? 'Address' : 'Name'} +
+
+
+ {#if activeTab === 'workers'} + Average Grade + {/if} +
+
+ {#if activeTab === 'workers'} + Tasks Completed + {:else} + Tasks Created + {/if} +
+
+ {#if activeTab === 'workers'} + $VIRAL Rewards + {:else} + $VIRAL Paid + {/if} +
+
+
+ +
+ {#each leaderboardData as item, index} +
+
+
+ {index} +
+
+ + +
+ {#if activeTab === 'workers' && 'address' in item} +
+ + {truncateAddress(item.address)} + + {#if item.nickname} + + {item.nickname} + + {:else if item.address === $walletAddress} + + {/if} +
+ {:else if 'name' in item} + {item.name} + {/if} +
+ + +
+
+ {#if activeTab === 'workers' && 'avgScore' in item} +
+ + {Math.floor(item.avgScore)}% + +
+ {/if} +
+
+
+ + {item.tasks} + Tasks + +
+
+
+
+ + {#if 'address' in item} + {formatCurrency(item.rewards)} $VIRAL + {:else} + {formatCurrency(item.payout)} $VIRAL + {/if} + +
+
+
+
+ {/each} +
+
+

+ {#if activeTab === 'workers'} + Sorted by Average Grade + {:else} + Sorted by $VIRAL Paid Out + {/if} +

+
+ + +
+

+ + Global Statistics +

+
+ +
+ Total Demonstrators + +
+
{totalWorkers}
+
+ +
+ Total Tasks Completed + +
+
+ {tasksCompleted} +
+
+ +
+ Total Paid Out + +
+
+ {formatCurrency(totalRewards)} $VIRAL +
+
+ +
+ Active Forges + +
+
{activeForges}
+
+
+
+ {:else} +
+ +
+ {/if} +
+
+ +