mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-30 23:14:37 +00:00
Handling the status of skills (#23)
* chore: bump version to 0.20.0 [skip ci] * revert: remove automatic skill dependency installation system - Removed dependency installation loading state from SkillSetupWizard - Reverted SkillRuntime dependency checking and installation methods - Cleaned up dependency-related imports and event listeners - Documented complete implementation in skills/todo.md for future work The dependency installation system was causing unwanted loading states when configuring skills. All functionality has been properly reverted while preserving implementation details for future development. * Refactor SkillManagementPanel to conditionally show action buttons based on connection status - Added offline and setup-required states with informative messages - Updated SkillSetupModal to use connection status for setup vs. manage mode - Simplified mode logic for better UX and error handling in setup scenarios * Update CLAUDE.md with new features, state slices, and code quality tools - Document additions: Redux aiSlice, skillsSlice, teamSlice, and architecture updates - Add ESLint, Prettier integrations with Husky hooks and GitHub workflows - Update development commands from npm to yarn for consistency - Expand key user groups and revise target audiences - Include enhanced project structure, build outputs, and CI/CD updates * fix: resolve TypeScript compilation error and enhance skill session synchronization - Fix unused parameter error in SkillManager.getSkillLoadParams() by prefixing with underscore - Add support for session parameter passing in SkillRuntime.load() method - Implement skill reloading capability for post-authentication updates - Ensure proper connection status synchronization between skill setup and UI display 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: change skill button text from 'Configure' to 'Manage' for connected skills - Update SkillsGrid button text to dynamically show based on connection status - Connected skills now show 'Manage' button instead of 'Configure' - Provides clearer user indication of skill state - Improves UX for authenticated skills like Telegram 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: update documentation, download system and code formatting improvements - Update Claude rules with improved project overview and command documentation - Enhance download screen with better architecture detection and formatting - Improve device detection utilities with multi-architecture support - Add skills system troubleshooting documentation - Code formatting improvements with better line breaks and spacing - Optimize GitHub release asset parsing for platform-specific downloads 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Steven Enamakel <31011319+senamakel@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
Steven Enamakel
github-actions[bot] <github-actions[bot]@users.noreply.github.com>
parent
9d7472131f
commit
9b3dfd0c2d
@@ -1,14 +1,14 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import {
|
||||
type Architecture,
|
||||
type ArchitectureDownloadLink,
|
||||
detectPlatform,
|
||||
fetchLatestRelease,
|
||||
getDownloadLink,
|
||||
getPlatformDisplayName,
|
||||
parseReleaseAssets,
|
||||
parseReleaseAssetsByArchitecture,
|
||||
type Architecture,
|
||||
type ArchitectureDownloadLink,
|
||||
type Platform,
|
||||
type PlatformArchitectureLinks,
|
||||
type PlatformDownloadLinks,
|
||||
@@ -33,7 +33,9 @@ const DownloadScreen = () => {
|
||||
const [selectedPlatform, setSelectedPlatform] = useState<Platform | null>(null);
|
||||
const [selectedArchitecture, setSelectedArchitecture] = useState<Architecture | null>(null);
|
||||
const [releaseLinks, setReleaseLinks] = useState<PlatformDownloadLinks | null>(null);
|
||||
const [architectureLinks, setArchitectureLinks] = useState<PlatformArchitectureLinks | null>(null);
|
||||
const [architectureLinks, setArchitectureLinks] = useState<PlatformArchitectureLinks | null>(
|
||||
null
|
||||
);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
@@ -63,7 +65,9 @@ const DownloadScreen = () => {
|
||||
const platformArchLinks = archLinks[detected.platform as keyof PlatformArchitectureLinks];
|
||||
if (platformArchLinks && platformArchLinks.length > 0) {
|
||||
// Prefer detected architecture, otherwise use first available
|
||||
const preferredLink = platformArchLinks.find(link => link.architecture === detected.architecture) || platformArchLinks[0];
|
||||
const preferredLink =
|
||||
platformArchLinks.find(link => link.architecture === detected.architecture) ||
|
||||
platformArchLinks[0];
|
||||
setSelectedArchitecture(preferredLink.architecture);
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -88,7 +92,8 @@ const DownloadScreen = () => {
|
||||
return getDownloadLink(selectedPlatform || 'unknown', releaseLinks || undefined);
|
||||
}
|
||||
|
||||
const platformArchLinks = architectureLinks[selectedPlatform as keyof PlatformArchitectureLinks];
|
||||
const platformArchLinks =
|
||||
architectureLinks[selectedPlatform as keyof PlatformArchitectureLinks];
|
||||
if (platformArchLinks && selectedArchitecture) {
|
||||
const link = platformArchLinks.find(l => l.architecture === selectedArchitecture);
|
||||
if (link) {
|
||||
@@ -139,9 +144,7 @@ const DownloadScreen = () => {
|
||||
{error && !isLoading && (
|
||||
<div className="mb-6">
|
||||
<div className="bg-red-500/10 border border-red-500/20 rounded-xl p-4">
|
||||
<p className="text-sm text-red-400">
|
||||
{error}. Using fallback download links.
|
||||
</p>
|
||||
<p className="text-sm text-red-400">{error}. Using fallback download links.</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -209,9 +212,12 @@ const DownloadScreen = () => {
|
||||
{downloadOptions
|
||||
.filter(opt => opt.platform !== selectedPlatform)
|
||||
.map(option => {
|
||||
const platformArchLinks = architectureLinks?.[option.platform as keyof PlatformArchitectureLinks];
|
||||
const platformArchLinks =
|
||||
architectureLinks?.[option.platform as keyof PlatformArchitectureLinks];
|
||||
const hasValidLink = platformArchLinks && platformArchLinks.length > 0;
|
||||
const defaultLink = platformArchLinks?.[0]?.url || getDownloadLink(option.platform, releaseLinks || undefined);
|
||||
const defaultLink =
|
||||
platformArchLinks?.[0]?.url ||
|
||||
getDownloadLink(option.platform, releaseLinks || undefined);
|
||||
const hasMultipleArchs = platformArchLinks && platformArchLinks.length > 1;
|
||||
|
||||
return (
|
||||
|
||||
@@ -408,7 +408,7 @@ export default function SkillsGrid() {
|
||||
setSetupModalOpen(true);
|
||||
}}
|
||||
className="px-4 py-1.5 text-xs font-medium text-primary-300 bg-primary-500/10 border border-primary-500/30 rounded-lg hover:bg-primary-500/20 transition-colors flex-shrink-0 ml-3">
|
||||
Configure
|
||||
{connectionStatus === 'connected' ? 'Manage' : 'Configure'}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -145,50 +145,59 @@ export default function SkillManagementPanel({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Action buttons — single row */}
|
||||
<div className="pt-1">
|
||||
{!confirmDisconnect ? (
|
||||
<div className="flex space-x-2">
|
||||
<button
|
||||
onClick={handleRestart}
|
||||
disabled={restarting}
|
||||
className="flex-1 px-3 py-2 text-xs font-medium text-white bg-stone-700/60 border border-stone-600/50 rounded-xl hover:bg-stone-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{restarting ? "Restarting..." : "Restart"}
|
||||
</button>
|
||||
{onReconfigure && (
|
||||
{/* Action buttons — only show if skill is actually running */}
|
||||
{connectionStatus !== "offline" && connectionStatus !== "setup_required" ? (
|
||||
<div className="pt-1">
|
||||
{!confirmDisconnect ? (
|
||||
<div className="flex space-x-2">
|
||||
<button
|
||||
onClick={onReconfigure}
|
||||
className="flex-1 px-3 py-2 text-xs font-medium text-primary-300 bg-primary-500/10 border border-primary-500/30 rounded-xl hover:bg-primary-500/20 transition-colors"
|
||||
onClick={handleRestart}
|
||||
disabled={restarting}
|
||||
className="flex-1 px-3 py-2 text-xs font-medium text-white bg-stone-700/60 border border-stone-600/50 rounded-xl hover:bg-stone-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Re-run Setup
|
||||
{restarting ? "Restarting..." : "Restart"}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setConfirmDisconnect(true)}
|
||||
className="flex-1 px-3 py-2 text-xs font-medium text-coral-400 bg-coral-500/10 border border-coral-500/30 rounded-xl hover:bg-coral-500/20 transition-colors"
|
||||
>
|
||||
Disconnect
|
||||
</button>
|
||||
{onReconfigure && (
|
||||
<button
|
||||
onClick={onReconfigure}
|
||||
className="flex-1 px-3 py-2 text-xs font-medium text-primary-300 bg-primary-500/10 border border-primary-500/30 rounded-xl hover:bg-primary-500/20 transition-colors"
|
||||
>
|
||||
Re-run Setup
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setConfirmDisconnect(true)}
|
||||
className="flex-1 px-3 py-2 text-xs font-medium text-coral-400 bg-coral-500/10 border border-coral-500/30 rounded-xl hover:bg-coral-500/20 transition-colors"
|
||||
>
|
||||
Disconnect
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex space-x-2">
|
||||
<button
|
||||
onClick={() => setConfirmDisconnect(false)}
|
||||
className="flex-1 px-3 py-2 text-xs font-medium text-stone-400 bg-stone-800/50 border border-stone-700 rounded-xl hover:bg-stone-800 transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={handleDisconnect}
|
||||
disabled={disconnecting}
|
||||
className="flex-1 px-3 py-2 text-xs font-medium text-white bg-coral-500 rounded-xl hover:bg-coral-600 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{disconnecting ? "Disconnecting..." : "Confirm Disconnect"}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
// Show message for offline skills
|
||||
<div className="pt-1">
|
||||
<div className="text-xs text-stone-500 text-center py-2 px-3 bg-stone-800/30 rounded-xl border border-stone-700/30">
|
||||
Skill is offline. Use "Re-run Setup" to reconnect.
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex space-x-2">
|
||||
<button
|
||||
onClick={() => setConfirmDisconnect(false)}
|
||||
className="flex-1 px-3 py-2 text-xs font-medium text-stone-400 bg-stone-800/50 border border-stone-700 rounded-xl hover:bg-stone-800 transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={handleDisconnect}
|
||||
disabled={disconnecting}
|
||||
className="flex-1 px-3 py-2 text-xs font-medium text-white bg-coral-500 rounded-xl hover:bg-coral-600 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{disconnecting ? "Disconnecting..." : "Confirm Disconnect"}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { useAppSelector } from "../../store/hooks";
|
||||
import { useSkillConnectionStatus } from "../../lib/skills/hooks";
|
||||
import SkillSetupWizard from "./SkillSetupWizard";
|
||||
import SkillManagementPanel from "./SkillManagementPanel";
|
||||
|
||||
@@ -27,13 +28,20 @@ export default function SkillSetupModal({
|
||||
onClose,
|
||||
}: SkillSetupModalProps) {
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
const setupComplete = useAppSelector(
|
||||
(state) => state.skills.skills[skillId]?.setupComplete,
|
||||
);
|
||||
// Skills without setup hooks always go straight to manage mode.
|
||||
const [mode, setMode] = useState<"manage" | "setup">(
|
||||
!hasSetup || setupComplete ? "manage" : "setup",
|
||||
);
|
||||
const skill = useAppSelector((state) => state.skills.skills[skillId]);
|
||||
const connectionStatus = useSkillConnectionStatus(skillId);
|
||||
|
||||
// Simplified mode logic: Only show manage mode when skill is actually working
|
||||
// Everything else (offline, errors, never connected, etc.) goes to setup mode
|
||||
const [mode, setMode] = useState<"manage" | "setup">(() => {
|
||||
// Only show manage mode when the skill is actually working
|
||||
const isWorking = connectionStatus === "connected" || connectionStatus === "connecting";
|
||||
|
||||
// For skills without setup hooks, still need to check if they're working
|
||||
// Show manage mode only if skill is currently working
|
||||
// Everything else goes to setup mode (first-time setup, errors, offline, disconnected)
|
||||
return isWorking ? "manage" : "setup";
|
||||
});
|
||||
|
||||
// Handle escape key
|
||||
useEffect(() => {
|
||||
@@ -137,7 +145,7 @@ export default function SkillSetupModal({
|
||||
<SkillSetupWizard
|
||||
skillId={skillId}
|
||||
onComplete={onClose}
|
||||
onCancel={setupComplete ? () => setMode("manage") : onClose}
|
||||
onCancel={skill?.setupComplete ? () => setMode("manage") : onClose}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -30,6 +30,7 @@ export default function SkillSetupWizard({
|
||||
}: SkillSetupWizardProps) {
|
||||
const [state, setState] = useState<WizardState>({ phase: "loading" });
|
||||
|
||||
|
||||
// Start the skill (if not running) then start the setup flow on mount
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
@@ -162,6 +163,7 @@ export default function SkillSetupWizard({
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
case "step":
|
||||
return (
|
||||
<SetupFormRenderer
|
||||
|
||||
Reference in New Issue
Block a user