Skip to content

Commit

Permalink
adding ui/chat handler
Browse files Browse the repository at this point in the history
  • Loading branch information
TerminalFi committed Jun 20, 2024
1 parent d13b02a commit 82301b5
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 64 deletions.
11 changes: 8 additions & 3 deletions Main.sublime-commands
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
[
{
"caption": "Copilot: Chat",
"caption": "Copilot: New Conversation",
"command": "copilot_conversation_create"
},
{
"caption": "Copilot: Continue",
"caption": "Copilot: Continue Last Conversation",
"command": "copilot_conversation_continue"
},
{
// Debug Command
"caption": "Copilot: Chat Agents",
"caption": "Copilot: Conversation Agents",
"command": "copilot_conversation_agents",
},
{
// Debug Command
"caption": "Copilot: Conversation Templates",
"command": "copilot_conversation_templates",
},
{
"caption": "Copilot: Check Status",
"command": "copilot_check_status"
Expand Down
2 changes: 2 additions & 0 deletions plugin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
CopilotConversationAgentsCommand,
CopilotConversationContinueCommand,
CopilotConversationCreateCommand,
CopilotConversationTemplatesCommand,
CopilotGetPanelCompletionsCommand,
CopilotGetVersionCommand,
CopilotNextCompletionCommand,
Expand Down Expand Up @@ -46,6 +47,7 @@
"CopilotConversationCreateCommand",
"CopilotConversationContinueCommand",
"CopilotConversationAgentsCommand",
"CopilotConversationTemplatesCommand",
# ST: event listeners
"EventListener",
"ViewEventListener",
Expand Down
5 changes: 5 additions & 0 deletions plugin/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
NTFY_LOG_MESSAGE,
NTFY_PANEL_SOLUTION,
NTFY_PANEL_SOLUTION_DONE,
NTFY_PROGRESS,
NTFY_STATUS_NOTIFICATION,
PACKAGE_NAME,
REQ_CHECK_STATUS,
Expand Down Expand Up @@ -293,6 +294,10 @@ def _handle_feature_flags_notification(self, payload: CopilotPayloadFeatureFlags
def _handle_log_message_notification(self, payload: CopilotPayloadLogMessage) -> None:
pass

@notification_handler(NTFY_PROGRESS)
def _handle_progress_notification(self, payload) -> None:
print(payload)

@notification_handler(NTFY_PANEL_SOLUTION)
def _handle_panel_solution_notification(self, payload: CopilotPayloadPanelSolution) -> None:
if not (view := ViewPanelCompletionManager.find_view_by_panel_id(payload["panelId"])):
Expand Down
107 changes: 47 additions & 60 deletions plugin/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@
from LSP.plugin.core.registry import LspTextCommand, LspWindowCommand
from LSP.plugin.core.url import filename_to_uri
from lsp_utils.helpers import rmtree_ex
from mdpopups import md2html

from .client import CopilotPlugin
from .constants import (
PACKAGE_NAME,
REQ_CHECK_STATUS,
REQ_CONVERSATION_AGENTS,
REQ_CONVERSATION_CREATE,
REQ_CONVERSATION_PRECONDITIONS,
REQ_CONVERSATION_TEMPLATES,
REQ_CONVERSATION_TURN,
REQ_FILE_CHECK_STATUS,
REQ_GET_PANEL_COMPLETIONS,
Expand All @@ -33,6 +34,7 @@
REQ_SIGN_OUT,
)
from .types import (
CopilotPayloadConversationTemplate,
CopilotPayloadFileStatus,
CopilotPayloadGetVersion,
CopilotPayloadNotifyAccepted,
Expand All @@ -44,7 +46,7 @@
CopilotRequestCoversationAgent,
T_Callable,
)
from .ui import ViewCompletionManager, ViewPanelCompletionManager
from .ui import ViewCompletionManager, ViewConversationManager, ViewPanelCompletionManager
from .utils import (
find_view_by_id,
get_session_setting,
Expand Down Expand Up @@ -180,6 +182,7 @@ def run(self, view_id: int | None = None) -> None:
class CopilotConversationCreateCommand(CopilotTextCommand):
@_provide_plugin_session()
def run(self, plugin: CopilotPlugin, session: Session, _: sublime.Edit) -> None:
ViewConversationManager(self.view)
session.send_request(
Request(
REQ_CONVERSATION_PRECONDITIONS,
Expand Down Expand Up @@ -220,7 +223,7 @@ def _on_result_conversation_create(self, session, payload) -> None:
)

def _on_result_conversation_turn(self, session, payload) -> None:
# pass
ViewConversationManager(self.view).conversation_id = payload["conversationId"]
session.send_request(
Request(
REQ_CONVERSATION_TURN,
Expand Down Expand Up @@ -257,71 +260,37 @@ def _on_result_conversation_turn(self, session, payload) -> None:

class CopilotConversationContinueCommand(CopilotTextCommand):
@_provide_plugin_session()
def run(self, plugin: CopilotPlugin, session: Session, message: str):
session.send_request(
Request(
REQ_CONVERSATION_PRECONDITIONS,
{},
),
lambda x: self._on_result_conversation_preconditions(session, x),
)

@_provide_plugin_session()
def input(self, plugin: CopilotPlugin, session: Session, args):
if "message" not in args:
return CopilotConversationDialogTextInputHandler(
self.view,
# Dummy text until we figure it out
[
'<span class="system">System</span>: Hello, how can I help you?',
'<span class="user">User</span>: Am I working in Sublime Text?',
'<span class="system">System</span>: No, you are working in a IDE called vscode.',
md2html(
self.view,
"""
You can try this code below
<a href="copy">Copy</a>
```py
def log(x):
print(x)
```
""",
),
],
)

def _on_result_conversation_turn(self, session, payload) -> None:
def run(self, plugin: CopilotPlugin, session: Session, _: sublime.Edit, message: str, *args, **kwargs):
conversation_manager = ViewConversationManager(self.view)
session.send_request(
Request(
REQ_CONVERSATION_TURN,
{
"conversationId": payload["conversationId"],
"message": "Am I working in Sublime Text?",
"conversationId": conversation_manager.conversation_id,
"message": message,
"workDoneToken": "5", # Not sure where this comes from
# "doc": Ji.Type.Optional(Z0),
# "computeSuggestions": Ji.Type.Optional(Ji.Type.Boolean()),
"computeSuggestions": True,
# "references": Ji.Type.Optional(Ji.Type.Array(k8)),
# "source": Ji.Type.Optional(sd),
# "workspaceFolder": Ji.Type.Optional(Ji.Type.String()),
},
),
lambda x: print(x),
self._on_result_conversation_turn,
)

# {
# workDoneToken: no.Type.Union([no.Type.String(), no.Type.Number()]),
# conversationId: no.Type.String(),
# message: no.Type.String(),
# followUp: no.Type.Optional(
# no.Type.Object({ id: no.Type.String(), type: no.Type.String() }),
# ),
# options: no.Type.Optional(Mn),
# doc: no.Type.Optional(Z0),
# computeSuggestions: no.Type.Optional(no.Type.Boolean()),
# references: no.Type.Optional(no.Type.Array(k8)),
# workspaceFolder: no.Type.Optional(no.Type.String()),
# }
@_provide_plugin_session()
def input(self, plugin: CopilotPlugin, session: Session, args):
conversation_manager = ViewConversationManager(self.view)
if "messages" not in args:
print(args)
return CopilotConversationDialogTextInputHandler(
self.view,
[f"conversation: {conversation_manager.conversation_id}"] + conversation_manager.conversation_history,
)

def _on_result_conversation_turn(self, payload) -> None:
print(payload)


class CopilotConversationDialogTextInputHandler(sublime_plugin.TextInputHandler):
Expand All @@ -330,7 +299,7 @@ def __init__(self, view: sublime.View, conversation_history: list[str]) -> None:
self.conversation_history = conversation_history

def name(self) -> str:
return "User"
return "chat"

def placeholder(self) -> str:
return "Your message"
Expand Down Expand Up @@ -369,13 +338,11 @@ def confirm(self, text: str) -> None:


class CopilotConversationAgentsCommand(CopilotTextCommand):
requirement = REQUIRE_NOTHING

@_provide_plugin_session()
def run(self, plugin: CopilotPlugin, session: Session, _: sublime.Edit) -> None:
session.send_request(Request("conversation/agents", {"options": {}}), self._on_result_check_status)
session.send_request(Request(REQ_CONVERSATION_AGENTS, {"options": {}}), self._on_result_coversation_agents)

def _on_result_check_status(self, payload: list[CopilotRequestCoversationAgent]) -> None:
def _on_result_coversation_agents(self, payload: list[CopilotRequestCoversationAgent]) -> None:
window = self.view.window()
if not window:
return
Expand All @@ -386,6 +353,26 @@ def is_visible(self, plugin: CopilotPlugin, session: Session) -> bool:
return get_session_setting(session, "debug")


class CopilotConversationTemplatesCommand(CopilotTextCommand):
@_provide_plugin_session()
def run(self, plugin: CopilotPlugin, session: Session, _: sublime.Edit) -> None:
session.send_request(
Request(REQ_CONVERSATION_TEMPLATES, {"options": {}}), self._on_result_conversation_templates
)

def _on_result_conversation_templates(self, payload: list[CopilotPayloadConversationTemplate]) -> None:
window = self.view.window()
if not window:
return
window.show_quick_panel(
[(item["id"], item["description"], ", ".join(item["scopes"])) for item in payload], lambda x: None
)

@_provide_plugin_session(failed_return=False)
def is_visible(self, plugin: CopilotPlugin, session: Session) -> bool:
return get_session_setting(session, "debug")


class CopilotAcceptCompletionCommand(CopilotTextCommand):
@_provide_plugin_session()
def run(self, plugin: CopilotPlugin, session: Session, edit: sublime.Edit) -> None:
Expand Down
3 changes: 2 additions & 1 deletion plugin/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
REQ_CONVERSATION_PRECONDITIONS = "conversation/preconditions"
REQ_CONVERSATION_PERSISTANCE = "conversation/persistance"
REQ_CONVERSATION_CREATE = "conversation/create"
REQ_CONVERSATION_CREATE = "conversation/create"
REQ_CONVERSATION_AGENTS = "conversation/agents"
REQ_CONVERSATION_TURN = "conversation/turn"
REQ_CONVERSATION_TURN_DELETE = "conversation/turnDelete"
REQ_CONVERSATION_DESTROY = "conversation/destroy"
Expand All @@ -48,6 +48,7 @@

NTFY_FEATURE_FLAGS_NOTIFICATION = "featureFlagsNotification" # done
NTFY_LOG_MESSAGE = "LogMessage" # done
NTFY_PROGRESS = "$/progress"
NTFY_PANEL_SOLUTION = "PanelSolution" # done
NTFY_PANEL_SOLUTION_DONE = "PanelSolutionsDone" # done
NTFY_STATUS_NOTIFICATION = "statusNotification" # done
16 changes: 16 additions & 0 deletions plugin/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,22 @@ class CopilotPayloadPanelCompletionSolutionCount(TypedDict, total=True):
# --------------------- #


class CopilotPayloadConversationEntry(TypedDict, total=True):
kind: str
conversationId: str
turnId: str
reply: str
annotations: list[str]
hideText: bool


class CopilotPayloadConversationTemplate(TypedDict, total=True):
id: str
description: str
shortDescription: str
scopes: list[str]


# class CopilotRequestCoversationPreconditions(TypedDict, total=True):
# pass

Expand Down
2 changes: 2 additions & 0 deletions plugin/ui/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from __future__ import annotations

from .chat import ViewConversationManager
from .completion import ViewCompletionManager
from .panel_completion import ViewPanelCompletionManager

__all__ = (
"ViewCompletionManager",
"ViewConversationManager",
"ViewPanelCompletionManager",
)
68 changes: 68 additions & 0 deletions plugin/ui/chat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from __future__ import annotations

import sublime

from ..types import CopilotPayloadConversationEntry
from ..utils import (
get_copilot_view_setting,
set_copilot_view_setting,
)


class ViewConversationManager:
# ------------- #
# view settings #
# ------------- #

@property
def is_visible(self) -> bool:
"""Whether the panel completions is visible."""
return get_copilot_view_setting(self.view, "is_visible_conversation", False)

@is_visible.setter
def is_visible(self, value: bool) -> None:
set_copilot_view_setting(self.view, "is_visible_conversation", value)

@property
def conversation_id(self) -> str:
"""Whether the panel completions is visible."""
return get_copilot_view_setting(self.view, "conversation_id", "")

@conversation_id.setter
def conversation_id(self, value: str) -> None:
set_copilot_view_setting(self.view, "conversation_id", value)

@property
def is_waiting(self) -> bool:
"""Whether the converation completions is streaming."""
return get_copilot_view_setting(self.view, "is_waiting_conversation", False)

@is_waiting.setter
def is_waiting(self, value: bool) -> None:
set_copilot_view_setting(self.view, "is_waiting_conversation", value)

@property
def completions(self) -> list[CopilotPayloadConversationEntry]:
"""All `completions` in the view. Note that this is a copy."""
return get_copilot_view_setting(self.view, "conversation_entries", [])

@completions.setter
def completions(self, value: list[CopilotPayloadConversationEntry]) -> None:
set_copilot_view_setting(self.view, "conversation_entries", value)

# -------------- #
# normal methods #
# -------------- #

def __init__(self, view: sublime.View) -> None:
self.view = view
self.conversation_history = []

def reset(self) -> None:
self.is_waiting = False
self.is_visible = False

def append_conversation_entry(self, entry: CopilotPayloadConversationEntry) -> None:
conversation_history = self.conversation_history
conversation_history.append(entry)
self.conversation_history = conversation_history

0 comments on commit 82301b5

Please sign in to comment.