File size: 5,523 Bytes
ab07a82
23ccecc
ab07a82
23ccecc
 
ab07a82
23ccecc
 
 
 
 
ab07a82
 
 
 
23ccecc
 
ab07a82
23ccecc
 
 
 
 
 
 
 
 
 
 
 
 
 
608dc33
23ccecc
ab07a82
23ccecc
 
 
 
 
ab07a82
 
23ccecc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ab07a82
 
 
23ccecc
 
ab07a82
23ccecc
 
 
608dc33
 
23ccecc
 
 
 
 
 
 
 
 
 
ab07a82
 
 
 
23ccecc
608dc33
 
23ccecc
 
ab07a82
 
 
 
 
 
23ccecc
ab07a82
 
 
 
 
 
 
23ccecc
 
 
 
 
 
 
 
 
 
 
 
ab07a82
 
 
 
23ccecc
ab07a82
 
 
23ccecc
 
 
 
 
 
 
 
ab07a82
 
23ccecc
ab07a82
 
 
 
608dc33
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import gradio as gr
from huggingface_hub import HfApi, CommitOperationDelete

def fetch_files(source_repo, hf_token):
    # Input validation
    if not hf_token.strip():
        yield gr.update(), gr.update(), "Error: Hugging Face Access Token is required.", []
        return
    if not source_repo.strip():
        yield gr.update(), gr.update(), "Error: Source repository name is required.", []
        return
    
    api = HfApi(token=hf_token.strip())
    
    try:
        # Initial status update before fetching
        yield gr.update(), gr.update(), f"Status: Fetching file list from '{source_repo}'...", []
        
        # Fetch the flat list of files from the repository
        files = api.list_repo_files(repo_id=source_repo.strip(), repo_type="model")
        files.sort()
        
        # Build a visual Linux-like tree representation for the CheckboxGroup
        choices = []
        for f in files:
            parts = f.split('/')
            if len(parts) == 1:
                display = f"πŸ“„ {f}"
            else:
                folder_path = "/".join(parts[:-1])
                display = f"πŸ“ {folder_path}/ β”œβ”€β”€ πŸ“„ {parts[-1]}"
            
            # Gradio allows (display_name, value) tuples for CheckboxGroup choices
            choices.append((display, f))
            
        yield (
            gr.update(visible=True), 
            gr.update(choices=choices, value=files), 
            f"Success: Fetched {len(files)} files. Uncheck the ones you do not want to copy.",
            files
        )
        
    except Exception as e:
        yield gr.update(visible=False), gr.update(), f"An error occurred: {str(e)}", []


def clone_repository(source_repo, target_repo, hf_token, selected_files, all_files):
    if not target_repo.strip():
        yield "Error: Target repository name is required."
        return
    
    api = HfApi(token=hf_token.strip())
    
    try:
        yield f"Status: Initiating server-side clone of '{source_repo}' to '{target_repo}'..."
        
        # 1. Duplicate the entire repo first (Server-side, almost instant, 0 disk space)
        api.duplicate_repo(
            from_id=source_repo.strip(),
            to_id=target_repo.strip(),
            repo_type="model"
        )
        
        # 2. Determine which files to delete based on what the user unchecked
        files_to_delete = set(all_files) - set(selected_files)
        
        if files_to_delete:
            yield f"Status: Deleting {len(files_to_delete)} unselected files from '{target_repo}'..."
            
            # Use 'path_in_repo' instead of 'path' to comply with the HF SDK
            operations = [CommitOperationDelete(path_in_repo=f) for f in files_to_delete]
            
            # Execute deletion in a single commit natively on HF servers
            api.create_commit(
                repo_id=target_repo.strip(),
                repo_type="model",
                operations=operations,
                commit_message="Clean up: Remove unselected files during selective clone"
            )
        
        yield f"Success! Selective clone completed. View your model here: https://huggingface.co/{target_repo.strip()}"
        
    except Exception as e:
        yield f"An error occurred: {str(e)}"


# Notice: The 'theme' parameter has been removed from Blocks to satisfy Gradio 6.0 specs
with gr.Blocks(title="Selective HF Model Cloner") as demo:
    gr.Markdown("# πŸ“‘ Selective Hugging Face Model Cloner")
    gr.Markdown("Fetch files from a model repository, uncheck what you don't need, and clone via high-speed server-side duplication.")
    
    with gr.Row():
        hf_token = gr.Textbox(
            label="Hugging Face Access Token", 
            placeholder="hf_...", 
            type="password",
            info="Requires 'Write' permissions."
        )
    
    with gr.Row():
        source_repo = gr.Textbox(
            label="Source Model Repository", 
            placeholder="e.g., meta-llama/Llama-3.2-1B"
        )
        fetch_btn = gr.Button("1. Fetch Files", variant="secondary")
        
    # We use a Gradio State to quietly keep track of the complete list of files fetched
    all_files_state = gr.State([])
    
    # This group remains hidden until files are successfully fetched
    with gr.Group(visible=False) as selection_group:
        gr.Markdown("### πŸ—‚οΈ Repository Files")
        gr.Markdown("*All files are selected by default. Uncheck any files you do **not** want to carry over.*")
        
        file_checkboxes = gr.CheckboxGroup(label="Repository Tree", interactive=True)
        
        target_repo = gr.Textbox(
            label="Target Model Repository", 
            placeholder="e.g., your-username/my-custom-llama"
        )
        clone_btn = gr.Button("2. Clone Selected Files", variant="primary")
        
    output_logs = gr.Textbox(label="Execution Status / Logs", interactive=False)
    
    # Trigger Step 1: File Fetching
    fetch_btn.click(
        fn=fetch_files,
        inputs=[source_repo, hf_token],
        outputs=[selection_group, file_checkboxes, output_logs, all_files_state]
    )
    
    # Trigger Step 2: Cloning Process
    clone_btn.click(
        fn=clone_repository,
        inputs=[source_repo, target_repo, hf_token, file_checkboxes, all_files_state],
        outputs=output_logs
    )

if __name__ == "__main__":
    # The theme parameter is now passed into launch() per Gradio 6.0 rules
    demo.queue().launch(theme=gr.themes.Soft())