Upload 4 files
Browse files
README.md
CHANGED
@@ -1,12 +1,69 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# LLM LSP
|
2 |
+
|
3 |
+
A proof of concept for intelligent document editing that works like humans do - scan, jump, edit.
|
4 |
+
|
5 |
+
## The Idea
|
6 |
+
Instead of making AI read entire documents, we let it:
|
7 |
+
1. Scan headers to find location (like grep)
|
8 |
+
2. Jump to right section (like sed)
|
9 |
+
3. Make targeted changes
|
10 |
+
4. Put it back (like cat)
|
11 |
+
|
12 |
+
## Quick Start
|
13 |
+
```python
|
14 |
+
pip install -r requirements.txt # openai, gradio
|
15 |
+
|
16 |
+
from lsp import LSP
|
17 |
+
editor = LSP(api_key="your-key")
|
18 |
+
editor.edit("doc.md", "add version warning")
|
19 |
+
```
|
20 |
+
|
21 |
+
## How It Works
|
22 |
+
|
23 |
+
1. **Map Sections Fast**
|
24 |
+
```python
|
25 |
+
sections = find_sections("doc.md") # Uses grep-like scan
|
26 |
+
```
|
27 |
+
|
28 |
+
2. **AI Picks Target**
|
29 |
+
```json
|
30 |
+
{
|
31 |
+
"sections": {"## Setup": 10, "## Config": 50},
|
32 |
+
"task": "add version warning",
|
33 |
+
"target": "## Setup at line 10"
|
34 |
+
}
|
35 |
+
```
|
36 |
+
|
37 |
+
3. **Extract & Edit**
|
38 |
+
```python
|
39 |
+
content = extract_section(file, 10, 49) # Just that piece
|
40 |
+
new_content = ai.modify(content) # AI edits small part
|
41 |
+
```
|
42 |
+
|
43 |
+
4. **Put It Back**
|
44 |
+
```python
|
45 |
+
replace_section(file, 10, 49, new_content)
|
46 |
+
```
|
47 |
+
|
48 |
+
## Try It
|
49 |
+
```bash
|
50 |
+
python main.py # Launches Gradio UI
|
51 |
+
```
|
52 |
+
|
53 |
+
## Status
|
54 |
+
- POC stage
|
55 |
+
- Works with markdown files
|
56 |
+
- Uses gpt-4o-mini
|
57 |
+
- Basic UI
|
58 |
+
|
59 |
+
## Limits
|
60 |
+
- One section at a time
|
61 |
+
- Just markdown for now
|
62 |
+
- No fancy error handling
|
63 |
+
- Will fuck up if sections aren't clear
|
64 |
+
|
65 |
+
## Next Steps?
|
66 |
+
- Handle multiple sections
|
67 |
+
- Better section detection
|
68 |
+
- More formats
|
69 |
+
- Whatever you need it to do
|
app.py
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from ui import LSPUI
|
2 |
+
from utils import logger, log_step
|
3 |
+
|
4 |
+
if __name__ == "__main__":
|
5 |
+
log_step("MAIN", "Starting SmartEdit application")
|
6 |
+
app = LSPUI()
|
7 |
+
app.launch()
|
ui.py
ADDED
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
from models import LSP
|
3 |
+
from utils import logger, log_step
|
4 |
+
import asyncio
|
5 |
+
from typing import Optional
|
6 |
+
|
7 |
+
class LSPUI:
|
8 |
+
def __init__(self):
|
9 |
+
self.lsp: Optional[LSP] = None
|
10 |
+
self.setup_interface()
|
11 |
+
|
12 |
+
def validate_inputs(self, api_key: str, file: gr.File, instruction: str):
|
13 |
+
"""Validate input parameters"""
|
14 |
+
if not api_key:
|
15 |
+
return False, "Error: API key required"
|
16 |
+
if not file:
|
17 |
+
return False, "Error: No file uploaded"
|
18 |
+
if not instruction:
|
19 |
+
return False, "Error: No edit instruction"
|
20 |
+
return True, None
|
21 |
+
|
22 |
+
def read_file_content(self, file: gr.File) -> str:
|
23 |
+
"""Read content from file upload"""
|
24 |
+
if isinstance(file, str):
|
25 |
+
with open(file, 'r') as f:
|
26 |
+
return f.read()
|
27 |
+
return file.read().decode('utf-8')
|
28 |
+
|
29 |
+
async def process_smart(self, api_key: str, file: gr.File, instruction: str):
|
30 |
+
"""Handle smart approach independently"""
|
31 |
+
try:
|
32 |
+
# Validate and initialize
|
33 |
+
valid, error = self.validate_inputs(api_key, file, instruction)
|
34 |
+
if not valid:
|
35 |
+
return error, error, error
|
36 |
+
|
37 |
+
if not self.lsp:
|
38 |
+
self.lsp = LSP(api_key)
|
39 |
+
|
40 |
+
content = self.read_file_content(file)
|
41 |
+
log_step("UI", "Starting smart approach")
|
42 |
+
|
43 |
+
# Process
|
44 |
+
result, traces, elapsed_time = await self.lsp.edit_smart(content, instruction)
|
45 |
+
log_step("UI", f"Smart approach completed in {elapsed_time:.2f}s")
|
46 |
+
|
47 |
+
return (
|
48 |
+
result,
|
49 |
+
"\n".join(traces),
|
50 |
+
f"β Completed in {elapsed_time:.2f}s"
|
51 |
+
)
|
52 |
+
except Exception as e:
|
53 |
+
error_msg = f"Error: {str(e)}"
|
54 |
+
log_step("UI", f"Error in smart approach: {error_msg}")
|
55 |
+
return error_msg, error_msg, "β Failed"
|
56 |
+
|
57 |
+
async def process_naive(self, api_key: str, file: gr.File, instruction: str):
|
58 |
+
"""Handle naive approach independently"""
|
59 |
+
try:
|
60 |
+
# Validate and initialize
|
61 |
+
valid, error = self.validate_inputs(api_key, file, instruction)
|
62 |
+
if not valid:
|
63 |
+
return error, error, error
|
64 |
+
|
65 |
+
if not self.lsp:
|
66 |
+
self.lsp = LSP(api_key)
|
67 |
+
|
68 |
+
content = self.read_file_content(file)
|
69 |
+
log_step("UI", "Starting naive approach")
|
70 |
+
|
71 |
+
# Process
|
72 |
+
result, traces, elapsed_time = await self.lsp.edit_naive(content, instruction)
|
73 |
+
log_step("UI", f"Naive approach completed in {elapsed_time:.2f}s")
|
74 |
+
|
75 |
+
return (
|
76 |
+
result,
|
77 |
+
"\n".join(traces),
|
78 |
+
f"β Completed in {elapsed_time:.2f}s"
|
79 |
+
)
|
80 |
+
except Exception as e:
|
81 |
+
error_msg = f"Error: {str(e)}"
|
82 |
+
log_step("UI", f"Error in naive approach: {error_msg}")
|
83 |
+
return error_msg, error_msg, "β Failed"
|
84 |
+
|
85 |
+
def setup_interface(self):
|
86 |
+
"""Setup the Gradio interface"""
|
87 |
+
with gr.Blocks(title="LSP Comparison") as self.blocks:
|
88 |
+
gr.Markdown("# LLM Selective Processing Demo")
|
89 |
+
|
90 |
+
with gr.Row():
|
91 |
+
api_key = gr.Textbox(label="OpenAI API Key", type="password")
|
92 |
+
|
93 |
+
with gr.Row():
|
94 |
+
file_upload = gr.File(
|
95 |
+
label="Upload Markdown File",
|
96 |
+
file_types=[".md", ".txt"]
|
97 |
+
)
|
98 |
+
instruction = gr.Textbox(label="What to edit")
|
99 |
+
|
100 |
+
with gr.Row():
|
101 |
+
smart_btn = gr.Button("Update with LSP")
|
102 |
+
naive_btn = gr.Button("Update w/o LSP")
|
103 |
+
|
104 |
+
with gr.Row():
|
105 |
+
with gr.Column():
|
106 |
+
gr.Markdown("### Smart Approach (Section-Aware)")
|
107 |
+
smart_result = gr.Textbox(label="Result", lines=10, value="")
|
108 |
+
smart_trace = gr.Textbox(label="Traces", lines=10, value="")
|
109 |
+
smart_status = gr.Textbox(label="Status", value="")
|
110 |
+
|
111 |
+
with gr.Column():
|
112 |
+
gr.Markdown("### Naive Approach (Full Document)")
|
113 |
+
naive_result = gr.Textbox(label="Result", lines=10, value="")
|
114 |
+
naive_trace = gr.Textbox(label="Traces", lines=10, value="")
|
115 |
+
naive_status = gr.Textbox(label="Status", value="")
|
116 |
+
|
117 |
+
# Set up independent event handlers
|
118 |
+
smart_btn.click(
|
119 |
+
fn=lambda: ("Working on it...", "Processing...", "β³ Running..."),
|
120 |
+
outputs=[smart_result, smart_trace, smart_status]
|
121 |
+
).then(
|
122 |
+
fn=self.process_smart,
|
123 |
+
inputs=[api_key, file_upload, instruction],
|
124 |
+
outputs=[smart_result, smart_trace, smart_status]
|
125 |
+
)
|
126 |
+
|
127 |
+
naive_btn.click(
|
128 |
+
fn=lambda: ("Working on it...", "Processing...", "β³ Running..."),
|
129 |
+
outputs=[naive_result, naive_trace, naive_status]
|
130 |
+
).then(
|
131 |
+
fn=self.process_naive,
|
132 |
+
inputs=[api_key, file_upload, instruction],
|
133 |
+
outputs=[naive_result, naive_trace, naive_status]
|
134 |
+
)
|
135 |
+
|
136 |
+
def launch(self):
|
137 |
+
"""Launch the Gradio interface"""
|
138 |
+
self.blocks.launch()
|
utils.py
ADDED
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import logging
|
2 |
+
from datetime import datetime
|
3 |
+
|
4 |
+
# Set up logging
|
5 |
+
logging.basicConfig(
|
6 |
+
level=logging.INFO,
|
7 |
+
format='%(asctime)s - %(levelname)s - %(message)s',
|
8 |
+
handlers=[
|
9 |
+
logging.StreamHandler(),
|
10 |
+
logging.FileHandler('lsp.log')
|
11 |
+
]
|
12 |
+
)
|
13 |
+
logger = logging.getLogger(__name__)
|
14 |
+
|
15 |
+
SYSTEM_PROMPT = """You are part of a document editing system.
|
16 |
+
Follow these EXACT steps with ZERO deviation:
|
17 |
+
|
18 |
+
PHASE 1: FIND TARGET SECTION
|
19 |
+
Input you get:
|
20 |
+
- Section map like:
|
21 |
+
"# Introduction": 1
|
22 |
+
"## Setup Steps": 10
|
23 |
+
"## Hostname Management": 50
|
24 |
+
|
25 |
+
- Edit instruction like: "add intro about hostname importance"
|
26 |
+
|
27 |
+
You must: ONLY identify target section and line numbers
|
28 |
+
|
29 |
+
PHASE 2: SECTION MODIFICATION
|
30 |
+
Input you get:
|
31 |
+
- Only the content of target section
|
32 |
+
- The edit instruction
|
33 |
+
|
34 |
+
Example Trace:
|
35 |
+
1. Got map: {"### App Stop Sequence": 100, "## Hostname Management": 150}
|
36 |
+
Got instruction: "add intro about importance"
|
37 |
+
β I choose: "## Hostname Management" at line 150
|
38 |
+
|
39 |
+
2. Got section content:
|
40 |
+
## Hostname Management
|
41 |
+
- Default: `{app_name}.localhost`
|
42 |
+
β I add intro explaining hostname importance
|
43 |
+
|
44 |
+
Remember: You never see full document. You work only with:
|
45 |
+
1. Section headers + line numbers
|
46 |
+
2. Then JUST the section to modify
|
47 |
+
"""
|
48 |
+
|
49 |
+
def log_step(phase: str, msg: str, data: dict = None):
|
50 |
+
"""Utility function for consistent logging"""
|
51 |
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
|
52 |
+
logger.info(f"[{timestamp}] [{phase}] {msg}")
|
53 |
+
if data:
|
54 |
+
logger.info(f"[{timestamp}] Data: {data}")
|